Nested Forms in Rails

29 Sep 2017

While reading about creating forms in Rails, I came across Nested Forms. On the first glance it looked difficult, but as I progressed, I said to myself, “It’s not so bad!”.

In order to create Nested Forms, it is important to understand about the helpers provided by Rails.

1). accepts_nested_attributes_for: Imagine that we have a BookShelf which has many Books. And the Books contain a title and an author. This setup calls for nested attributes.

  class BookShelf < ActiveRecord::Base
    has_many :books
    accepts_nested_attributes_for :books
  end


accepts_nested_attributes_for provides us with books_attributes= (setter) method. Now, we can set the attributes of model books, through bookshelf hash. The params in this case will be.

params = {
  bookshelf: {
    size: "small",
    books_attributes: [
      {title: "Coders at Work", author: "Peter Seibel"},
      {title: "Design Patterns in Ruby", author: "Russ Olsen"},
      {title: "The Pragmatic Programmer", author: "Andy Hunt"}
    ]
  }
}


accepts_nested_attributes_for also provides us with some options:

  • reject_if: Helps us to ignore the hashes that fail the criteria or required validations.

      class BookShelf < ActiveRecord::Base
        has_many :books
        accepts_nested_attributes_for :books, reject_if: proc {
         |attribute| attribute['title'].blank?
        }
      end
    


  • allow_destroy: Allows us to delete the records trough attributes hash.

     class BookShelf < ActiveRecord::Base
         has_many :books
         accepts_nested_attributes_for :books, allow_destroy: true
      end
    


2). fields_for: This helper takes two arguments. The first argument is the model for which we want to create the attributes and the second is the object. It allows the methods to be called on builder in order to generate fields.


  <%= f.fields_for :books, Book.new do |book_attributes| %>
    <%= book_attributes.text_field :title %>
    <%= book_attributes.text_field :author %>
  <% end %>


Using these form helpers, we can create nested forms.

Step1. Add accepts_nested_attributes_for in the model whose hash will contain the nested attributes.

    class BookShelf < ActiveRecord::Base
      has_many :books
      accepts_nested_attributes_for :books
    end

As stated above, this code will provide us with books_attributes= method.

Step2. Update strong parameters in BookShelf controller so that it accepts the books_attributes.

    class BookShelvesController < ApplicationController
      def index
        @bookshelves = BookShelf.all
      end

      def show
        @bookshelf = BookShelf.find(params[:id])
      end

      def new
        @bookshelf = BookShelf.new
      end

      def create
        @bookshelf = BookShelf.new(bookshelf_params)
        if @bookshelf.save
          redirect_to bookshelf_path(@bookshelf)
        else
          render new_bookshelf_path
        end
      end

      private

      def bookshelf_params
        params.require(:bookshelf).permit(:size, books_attributes: [:title, :author])
      end
    end


Step 3. Updating the view. In our view, we use fields_for helper method.

    <div>
      <%= form_for :bookshelf do |f| %>
        <div>
          <%= f.label :size %>
          <%= f.text_field :size %>
        <div/>
        <div>
          <%= f.fields_for :books, Book.new do |book_attributes| %>
            <%= book_attributes.label :title %>
            <%= book_attributes.text_field :title %>
            <br/>
            <%= book_attributes.label :author %>
            <%= book_attributes.text_field :author %>
          <% end %>
        <div/>
        <%= f.submit %>
      <% end %>
    <div/>

Finally, login to rails server and check the form.