Recipe 3.10. Updating an Active Record Object


Problem

Your application needs the ability to update records in your database. These records may contain associations with other objects, and these associations may need to be updated, just like any other field.

For example, you have a database application for storing books during their creation. Your database schema defines books and inserts (coupons placed within the pages). You want to modify your application to allow you to update book objects by adding inserts. Specifically, a book can have several inserts, and an insert can belong to more than one book.

Solution

Your database containing books and inserts tables is defined with this migration:

db/migrate/001_build_db.rb:

class BuildDb < ActiveRecord::Migration   def self.up          create_table :books do |t|       t.column :name, :string       t.column :description, :text     end          create_table :inserts do |t|       t.column :name, :string     end          create_table :books_inserts, :id => false do |t|       t.column :book_id, :integer       t.column :insert_id, :integer     end     Insert.create :name => 'O\'Reilly Coupon'     Insert.create :name => 'Borders Coupon'     Insert.create :name => 'Amazon Coupon'   end   def self.down     drop_table :books     drop_table :inserts   end end

The third table created in the migration creates a link table between books and inserts. Now establish a has-and-belongs-to-many relationship between books and inserts within the following model class definitions:

app/models/book.rb:

class Book < ActiveRecord::Base   has_and_belongs_to_many :inserts end

app/models/insert.rb:

class Insert < ActiveRecord::Base   has_and_belongs_to_many :books end

Next, modify the edit method of the Books controller to store all inserts in the @inserts array. This being an instance array, it will be made available to the edit form.

app/controllers/books_controller.rb:

class BooksController < ApplicationController   def index     list     render :action => 'list'   end   def list     @book_pages, @books = paginate :books, :per_page => 10   end   def show     @book = Book.find(params[:id])   end   def new     @book = Book.new   end   def create     @book = Book.new(params[:book])     if @book.save       flash[:notice] = 'Book was successfully created.'       redirect_to :action => 'list'     else       render :action => 'new'     end   end   def edit     @book = Book.find(params[:id])     @inserts = Insert.find(:all, :order => "name desc")   end   def update     @book = Book.find(params[:id])     insert = Insert.find(params["insert"].to_i)     unless @book.inserts.include?(insert)       @book.inserts << insert     end     if @book.update_attributes(params[:book])       flash[:notice] = 'Book was successfully updated.'       redirect_to :action => 'show', :id => @book     else       render :action => 'edit'     end   end   def destroy     Book.find(params[:id]).destroy     redirect_to :action => 'list'   end end

Add a drop-down menu of inserts to the book edit form. This form submits to the update action of the Books controller, which has been modified to handle inserts.

app/views/books/edit.rhtml:

<h1>Editing book</h1> <% form_tag :action => 'update', :id => @book do %>   <%= render :partial => 'form' %>   <select name="insert">     <% for insert in @inserts  %>       <option value="<%= insert.id %>"><%= insert.name %></option>     <% end %>   </select>   <%= submit_tag 'Edit' %> <% end %> <%= link_to 'Show', :action => 'show', :id => @book %> | <%= link_to 'Back', :action => 'list' %>

Finally, add inserts to the display of each book, if any exist:

app/views/books/show.rhtml:

<% for column in Book.content_columns %> <p>   <b><%= column.human_name %>:</b> <%=h @book.send(column.name) %> </p> <% end %> <% if @book.inserts.length > 0 %>   <b>Inserts:</b>;   <ul>     <% for insert in @book.inserts %>       <li><%= insert.name %></li>     <% end %>   </ul> <% end %> <%= link_to 'Edit', :action => 'edit', :id => @book %> | <%= link_to 'Back', :action => 'list' %>

Discussion

Adding the details of a one-to-many relationship to a Rails application is a common next step after the generation of basic scaffolding. There are enough unknowns that having the scaffolding attempt to guess the details of a one-to-many relationship would not work. The good news is that a lot of helpful methods get added to your models when you create Active Record associations. These methods really simplify the CRUD of associations.

The solution adds a drop-down list of inserts to the book edit form. The form passes an insert ID to the BooksController. The controller's update method finds this insert ID in the params hash, converts it to an integer with to_i, and passes it to the find method of the Insert subclass of Active Record. After retrieving the insert object, we check to see if the book object that we're updating already contains that insert. If not, the insert object is appended to an array of Inserts with the << operator.

The rest of the book data is updated with a call to update_attributes which, like Active Record's create method, immediately attempts to save the object. If the save is a success, the solution redirects to the show action to display the newly updated book and its inserts.

Figure 3-3 shows the solution's edit screen.

Figure 3-3. The Book edit screen with a drop-down menu of Inserts


See Also

  • Section 5.12"




Rails Cookbook
Rails Cookbook (Cookbooks (OReilly))
ISBN: 0596527314
EAN: 2147483647
Year: 2007
Pages: 250
Authors: Rob Orsini

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net