Skip to content

Instantly share code, notes, and snippets.

@octosteve
Created March 7, 2013 01:57
Show Gist options
  • Save octosteve/5104960 to your computer and use it in GitHub Desktop.
Save octosteve/5104960 to your computer and use it in GitHub Desktop.
A tutorial on how to work nested forms in rails.
When we last left our application it there were list and task models. They were associated with a list having many tasks and a task belonging to a list.
We wanted to build a nested form so we had to let the list model know it has some additional responsibilities.
In the List model we had to add this:
accepts_nested_attributes_for :tasks
This line creates an attribute on the list objects available at :task_attributes.
Since rails has mass assignment protection, and we WANT our form to use mass assignment to create tasks through a list we need to white list our new attribute on the attr_accessible line
attr_accessible :name, :tasks_attributes
Model stuff done!
On to the view (Skipping the controller for now)
Assuming you built this with a scaffold generator, you have a _form partial in your lists view folder.
The idea we're going for is we want to be able to submit a form with a List name, and some tasks. We want it to automagically make the correct assignments.
Change your form from this
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
to this:
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<%= f.fields_for :tasks do |task_form| %>
<div>
<%= task_form.label :description %><br />
<%= task_form.text_field :description %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
The difference is that f.fields_for :tasks line.
It uses f, a form builder object (http://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html), to create a form beneath it.
It uses the object it was derived from (in this case @list) and creates a form for each of it's associated attributes.
In short, if we had
@list = List.new
@list.tasks.new
Then there would be one name field and one description field. If we did:
@list = List.new
3.times { @list.tasks.new }
We would get 3! description fields.
If you go to the page now, it won't have any descriptions since we need to set up empty tasks to populate the form with.
In our controllers new action, let's make a slot for 3 potential tasks.
def new
@list = List.new
+ 3.times { @list.tasks.new }
Now our form works!
For bonus points, let's change the show to show all of our tasks for each list.
Replace your show page with this:
<p id="notice"><%= notice %></p>
<p>
<h1>List</h1>
<b>Name:</b>
<%= @list.name %>
<% unless @list.tasks.empty? %>
<h2>Tasks</h2>
<% @list.tasks.each do |task| %>
<%= task.description %>
<% end %>
<% end %>
</p>
<%= link_to 'Edit', edit_list_path(@list) %> |
<%= link_to 'Back', lists_path %>
Happy Hacking!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment