Rails - Nested Forms in Three Steps

Posted by Rachel Hawa on April 24, 2019

Rails is rife with magic. Form helpers, in particular the ability to build forms directly linked to Ruby objects, are one piece of Rails magic that I had a hard time wrapping my head around.

This is a quick guide to setting up nested forms in rails.

First - what is a nested form? It is, as it’s name suggests, a form within a form. More specifically, the nested form is creating or manipulating a model that different from the model the parent form is for.

Let’s pretend we have two models - owners, and dogs. An owner can have many dogs, and a dog belongs to one owner.

class Owner < ActiveRecord::Base
 has_many :dogs
end

and

class Dog < ActiveRecord::Base
 belongs_to :owner
end

The form to create a new dog would look something like this:

<h1>Create a new Dog:</h1>

<%= form_for(@dog) do |f|%>

  <div>
    <%= f.label :name%>
    <%= f.text_field :name%>
  </div><br>
	
  <div>
    <%= f.label :breed%>
    <%= f.text_field :breed%>
  </div><br>
	
  <div>
    <%= f.label :age%>
    <%= f.text_field :age%>
  </div><br>
	
  <div>
    <label for="dog_owner_id">Select an owner:</label>
    <%= f.collection_select :owner_id, owner.all, :id, :name, prompt: true%>
  </div><br>
	
  <%= f.submit %>
	
<% end %>

The above form gives a the user the ability to select and owner from owners already saved to the database - but what if they need to create a new owner? It would be a terrible user experience to have the user realize this, then go to a different page to create an owner, then back to this page to create the dog. Nested forms to the rescue!

Step 1 - Model

Add the accepts_nested_attributes_for method to the model that our main form is for, in our example, Dogs. This is a class method that defines an attribute writer for the specified association (owners). You can read up on it here.

Our Owner class now looks like this:

class Dog < ActiveRecord::Base
 belongs_to :owner
 accepts_nested_attributes_for :owner
end

Step 2 - View

Next, we need to update our view by adding the nested form. To do this, we’ll use fields_for, which, like form_for yields a FormBuilder object.

<div>
  <h3>Or create a new owner:</h3>
  <%= f.fields_for :owner, Owner.new do |owner_attributes|%>
      <%= owner_attributes.label :name, "Owner Name:" %>
      <%= owner_attributes.text_field :name %>
  <% end %>
</div>

Our updated form looks like this:

<h1>Create a new Dog:</h1>

<%= form_for(@dog) do |f|%>

  <div>
    <%= f.label :name%>
    <%= f.text_field :name%>
  </div><br>
	
  <div>
    <%= f.label :breed%>
    <%= f.text_field :breed%>
  </div><br>
	
  <div>
    <%= f.label :age%>
    <%= f.text_field :age%>
  </div><br>
	
  <div>
    <label for="dog_owner_id">Select an owner:</label>
    <%= f.collection_select :owner_id, owner.all, :id, :name, prompt: true%>
  </div><br>

  <div>
    <h3>Or create a new owner:</h3>
    <%= f.fields_for :owner, Owner.new do |owner_attributes|%>
        <%= owner_attributes.label :name, "Owner Name:" %>
        <%= owner_attributes.text_field :name %>
    <% end %>
  </div>
	
  <%= f.submit %>
	
<% end %>

Step 3a - Controller

When a user submits the original form, the controller action is to create a new instance of a dog. With the nested form, we also need to create a new instance of an owner, and associate that owner with the dog. This is accomplished with the build method.

class DogsController < ApplicationController

  def new
   @dog = Dog.new
   @dog.build_owner 
  end

 end

Step 3b - Controller

We’ll also need to update our strong parameters in the controller. Using accepts_nested_attributes_for in the Dog model added a new attribute to the class.

class DogsController < ApplicationController

  def  dog_params
    params.require(:dog).permit(:name, :breed, :age owner_attributes: [:name])
  end
	
end