11.25.07

A rails form that creates multiple models at once

Posted in MySQL, Programming, Ruby, rails at 6:10 pm by Robert Horvick

I want to create a simple site that allows users to add event, restaurant and location details through an ajax-ish interface.  Ultimately I have a design in mind but to get there I need to be able to submit details of multiple models through a single form (there are about 10 fields over 4 models that will need to be defined).

I wanted to use the action view “form_for”, a single submit and have the submission automatically map the fields to their proper object so that my controller method could look like this (sans error handling):

def create
    if request.post?
@deal = Deal.new(params[:deal])
@deal.Restaurant = Restaurant.new(params[:restaurant])
@deal.Location = Location.new(params[:location])
      @deal.save
    end
  end

Long story short - what you need to do is use the “fields_for” call within the form to get the model information. The form in the view looks like:

<% form_for :deal, :url => { :action => :create } do |form| %>
  <% fields_for :restaurant do |r| %>
Restaurant: <%= r.text_field :name %><br/>
  <% end %>    

  <% fields_for :location do |l| %>
Address 1: <%= l.text_field :address1 %><br/>
Address 2: <%= l.text_field :address2 %><br/>
City: <%= l.text_field :city %><br/>
State: <%= l.text_field :state %><br/>
Zip: <%= l.text_field :zip %><br/>
  <% end %>    

  Details: <%= form.text_area :description, :rows => 3 %>
  <%= submit_tag %>
<% end %>

In this example three models are used - Deal, Restaurant and Location.

The form is based on Deal (since I want the submit to go to Deal::create and I defined two fields_for blocks - one for Restaurant and Location.

The params.inspect output of the form data is:

Parameters: {”restaurant”=>{”name”=>”rest name”}, “deal”=>{”description”=>”Kids can eat whatever they want in under 3 minutes.”}, “commit”=>”Save changes”, “location”=>{”city”=>”some place”, “address1″=>”address one”, “zip”=>”12345″, “address2″=>”", “state”=>”NC”}}

Notice that restaurant, deal and location are all their own hash which can be passed to the new method on the respective models.

Worked like a champ.

Details on the schema is:

    create_table :locations do |t|
      t.column :address1, :string, :limit => 60
      t.column :address2, :string, :limit => 60
      t.column :city, :string, :limit => 40
      t.column :state, :string, :limit => 2
      t.column :zip, :string, :limit => 10
      t.column :phone, :string, :limit => 16
t.column :lng, :string, :limit => 30
      t.column :lat, :string, :limit => 30
      t.column :restaurant_id, :int
    end    

    create_table :restaurants do |t|
      t.column :name, :string, :limit => 60
    end    

    create_table :deals do |t|
      t.column :restaurant_id, :int
      t.column :location_id, :int
      t.column :description, :text
    end

And the models relationships:

class Deal < ActiveRecord::Base
  belongs_to :Restaurant
  belongs_to :Location
...
end    

class Location < ActiveRecord::Base
  belongs_to  :Restaurant
end    

class Restaurant < ActiveRecord::Base
  has_many  :Location
  has_many  :Deal
...
end
[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

20 Comments »

  1. Mike said,

    November 28, 2007 at 9:25 pm

    This is very excellent little write-up. I have been struggling with this very issue for the past 2 days. Thanks so much. Keep up the awesome work.

  2. RD said,

    January 10, 2008 at 6:55 am

    Thanks for this, will this work even when editing the same record

  3. Xavier said,

    January 24, 2008 at 11:21 pm

    As has been said, great little write up. Just a heads up on relationships, the convention is to keep those declarations lower case and pluralize (rails does some magic for you).

    class Restaurant, for example:
    has_many :locations
    has_many :deals

  4. ben said,

    January 29, 2008 at 3:26 pm

    Thanks! Saved me tons of time.

  5. pierre said,

    February 6, 2008 at 4:28 am

    tx, just used that, couldn’t figure it out on my own…

  6. tea said,

    February 20, 2008 at 12:02 am

    hi there, ihave a problem.
    i followed ur tutorial but still i couldnt figure it out.
    hope u cant assist me.
    i would like to save students’ attendance. when i click the button, it will generate multiple records in the attendance table. here’s my code.

    ##—– students’ attendance —–##
    def eattendance
    @class1s = Class1.find(:all)
    @subjects = Subject.find(:all)
    end

    def addattendance
    @page = “addattendance”
    @attendance = Attendance.new
    @teachers = Teacher.find(:all)
    @students = Student.find(:all, :conditions => ["class1_id = ?", params[:class_1][:id]])
    end

    def createattend
    @attendance = Attendance.new(params[:attendance])
    #@attendance.class1_id = params[:class_1][:id]
    #@attendance.subject_id = params[:subject][:id]
    @attendance.class1_id = params[:class_1][:id] if params[:class_1] and params[:class_1][:id]
    @attendance.subject_id = params[:subject][:id] if params[:subject] and params[:subject][:id]
    @attendance.teacher_id = session[:user_id]
    if @attendance.save
    flash[:notice] = “Attendance added, thank you”
    redirect_to(:controller => ‘admin’, :action => ‘eattendance’)
    else
    render :action => ‘index’
    end
    end
    =================================
    #eattendance.rhtml

    School Information System


    E-Attendance
    ‘addattendance’ %>

    Date:
    [:day, :month, :year], :start_year => 2000, :end_year => Time.now.year)%>
    Select Class

    Please Select Class

    <option value=”">

    Select Subject

    Please Select Subject

    <option value=”">


     

     
     

    =================================
    #addattendance.rhtml

    School Information System


    E-Attendance
    ‘createattend’, :id => @attendance %>

    [:day, :month, :year], :start_year => 2000, :end_year => Time.now.year)%>

    Student Name & ID
    Attendance
    Remarks

    []

     

  7. tea said,

    February 20, 2008 at 12:03 am

    hi there, ihave a problem.
    i followed ur tutorial but still i couldnt figure it out.
    hope u cant assist me.
    i would like to save students’ attendance. when i click the button, it will generate multiple records in the attendance table. here’s my code.

    [code]##—– students’ attendance —–##
    def eattendance
    @class1s = Class1.find(:all)
    @subjects = Subject.find(:all)
    end

    def addattendance
    @page = “addattendance”
    @attendance = Attendance.new
    @teachers = Teacher.find(:all)
    @students = Student.find(:all, :conditions => ["class1_id = ?", params[:class_1][:id]])
    end

    def createattend
    @attendance = Attendance.new(params[:attendance])
    #@attendance.class1_id = params[:class_1][:id]
    #@attendance.subject_id = params[:subject][:id]
    @attendance.class1_id = params[:class_1][:id] if params[:class_1] and params[:class_1][:id]
    @attendance.subject_id = params[:subject][:id] if params[:subject] and params[:subject][:id]
    @attendance.teacher_id = session[:user_id]
    if @attendance.save
    flash[:notice] = “Attendance added, thank you”
    redirect_to(:controller => ‘admin’, :action => ‘eattendance’)
    else
    render :action => ‘index’
    end
    end
    =================================
    #eattendance.rhtml

    School Information System


    E-Attendance
    ‘addattendance’ %>

    Date:
    [:day, :month, :year], :start_year => 2000, :end_year => Time.now.year)%>
    Select Class

    Please Select Class

    <option value=”">

    Select Subject

    Please Select Subject

    <option value=”">


     

     
     

    =================================
    #addattendance.rhtml

    School Information System


    E-Attendance
    ‘createattend’, :id => @attendance %>

    [:day, :month, :year], :start_year => 2000, :end_year => Time.now.year)%>

    Student Name & ID
    Attendance
    Remarks

    []

     

    [/code]

  8. McRipper said,

    June 26, 2008 at 8:44 am

    Thanks!!

  9. The Rails-tarded way to inject related model info into your form « Wherever I go, there I am said,

    July 13, 2008 at 12:17 am

    [...] I was going to submit, I needed to embed it using fields_for, this process is described quite well here. The main difference between the standard use of fields_for and the way I’m using it is that [...]

  10. james said,

    July 20, 2008 at 4:48 pm

    How to you do validations on the second model that uses the fields_for?

  11. bj said,

    July 29, 2008 at 3:47 am

    thanks! you are great!

  12. Deepak said,

    August 26, 2008 at 8:00 pm

    This is exactly what I was looking for to get me off the ground using rails views. Thanks!

  13. Saty said,

    August 28, 2008 at 7:09 pm

    Hi..thx for the above solution..

    However, i am encountering an “undefined method ” error which links to the controller. Followig ur example, it is stating that “Restaurant” is an undefined method for “deal” where “@deal.Restaurant = Restaurant.new(params[:restaurant]) “.

    can i have a clarification on this issue..

    thx in advance.

  14. Walter said,

    September 1, 2008 at 3:32 pm

    Excellent. Thanks. How does update work for multiple models in one form? Thanks. Regards. Walter.

  15. Scott said,

    September 10, 2008 at 9:47 pm

    I am getting the same error as Saty?? Any suggestions?

  16. Neer said,

    September 23, 2008 at 7:04 pm

    This is great. I am having one issue with this though.

    I have a database table called “Rules” and one called “Checks”. Rules contains a foreign key, “checks_id”. When I use your method and hit “submit” I get an error from rails saying that checks_id cannot be NULL. This means that it did not add the newly created check id as the foreign key for the new rule. Do you have any idea how I can resolve this? Thanks again.

  17. joebva said,

    September 28, 2008 at 5:27 pm

    I am getting the same error as Saty and Scott. Did anyone figure it out?

  18. janus said,

    October 15, 2008 at 6:29 am

    This is great, you have saved from me from headache.

  19. siva said,

    October 31, 2008 at 7:51 am

    nice article

  20. Ben said,

    November 4, 2008 at 12:34 am

    Neer,
    I doubt you are still having the same problem, but what solved it for me was to make sure that belongs_to goes on the model that contains the foreign key column and has_one goes on the model that is the target of that foreign key.

Leave a Comment