11.25.07
A rails form that creates multiple models at once
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
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.
RD said,
January 10, 2008 at 6:55 am
Thanks for this, will this work even when editing the same record
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
ben said,
January 29, 2008 at 3:26 pm
Thanks! Saved me tons of time.
pierre said,
February 6, 2008 at 4:28 am
tx, just used that, couldn’t figure it out on my own…
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
[]
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]
McRipper said,
June 26, 2008 at 8:44 am
Thanks!!
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 [...]
james said,
July 20, 2008 at 4:48 pm
How to you do validations on the second model that uses the fields_for?
bj said,
July 29, 2008 at 3:47 am
thanks! you are great!
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!
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.
Walter said,
September 1, 2008 at 3:32 pm
Excellent. Thanks. How does update work for multiple models in one form? Thanks. Regards. Walter.
Scott said,
September 10, 2008 at 9:47 pm
I am getting the same error as Saty?? Any suggestions?
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.
joebva said,
September 28, 2008 at 5:27 pm
I am getting the same error as Saty and Scott. Did anyone figure it out?
janus said,
October 15, 2008 at 6:29 am
This is great, you have saved from me from headache.
siva said,
October 31, 2008 at 7:51 am
nice article
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.