Topic: Creating Many Models in One Form
Earlier I showed you how to create two models in one form. In this article I will show you how to create many models (more than two) in a single form. Just like in the other article, project has many tasks.
In the new action we need to set up a project with a few tasks. Let's say 5.
# in projects_controller.rb
def new
@project = Project.new
5.times { @project.tasks.build }
end
In the view we need to loop through the project's tasks and display the fields for each one:
# in projects/new.rhtml
<% for task in @project.tasks %>
<p><%= text_field :task, :name %></p>
<% end %>
The problem with this is that every field will have the same name, so when we submit the form only one field will be sent. We need to find some way to identify each field to give it a unique name. An easy way to do this is to add the index of the array to the name of the field using the fields_for helper.
# in projects/new.rhtml
<% @project.tasks.each_with_index do |task, index| %>
<% fields_for "tasks[#{index}]", task do |f| %>
<p><%= f.text_field :name %></p>
<% end %>
<% end %>
When the form is submitted, the values for the tasks will be in the params[:tasks] hash. We can loop through this to build the tasks for the project and save them to the database. Like this:
# in projects_controller.rb
def create
@project = Project.new(params[:project])
params[:tasks].each_value { |task| @project.tasks.build(task) }
if @project.save
redirect_to :action => 'index'
else
render :action => 'new'
end
end
There is one more problem we need to solve. If the task is left blank, we don't want to add it to the project. We can check for blank tasks easily enough inside that build loop:
# in projects_controller.rb
params[:tasks].each_value do |task|
@project.tasks.build(task) unless task.values.all?(&:blank?)
end
That's it. Here's the entire code:
# in projects_controller.rb
def new
@project = Project.new
5.times { @project.tasks.build }
enddef create
@project = Project.new(params[:project])
params[:tasks].each_value do |task|
@project.tasks.build(task) unless task.values.all?(&:blank?)
end
if @project.save
redirect_to :action => 'index'
else
render :action => 'new'
end
end# in projects/new.rhtml
<%= start_form_tag :action => 'create' %>
<p>Project Name: <%= text_field :project, :name %></p>
<h2>Tasks</h2>
<% @project.tasks.each_with_index do |task, index| %>
<% fields_for "tasks[#{index}]", task do |f| %>
<p><%= f.text_field :name %></p>
<% end %>
<% end %>
<%= end_form_tag %>
In the next article I show you how to remove/add tasks dynamically in one form.
Last edited by ryanb (2006-11-06 02:22:48)