Updating an Active Record ObjectProblemYour 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. SolutionYour 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' %> DiscussionAdding 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 InsertsSee Also |