Search code examples
ruby-on-railsformsnested-formscocoon-gem

Creating a nested two level form in Rails


I have a Restaurant model that has many DishCategory's and also has many Dish's through DishCategory's. So for instance, the category Dinner would be a number of dishes associated with it. To help build the form I'm using the cocoon gem.

I'm familiar with setting up the view code for a traditional nested forms for but now that I'm going one level deeper I'm not sure how to write and where to put the inputs for dishes. Do I setup another simple_fields_for block for dishes inside my partial? Following the docs this is how things are currently setup:

Models

class Restaurant < ActiveRecord::Base
  has_many :dish_categories, dependent: :destroy
  has_many :dishes, through: :dish_categories, dependent: :destroy

  accepts_nested_attributes_for :dish_categories, :reject_if => :all_blank, :allow_destroy => true
end

class DishCategory < ActiveRecord::Base
  belongs_to :restaurant

  has_many :dishes, dependent: :destroy

  accepts_nested_attributes_for :dishes, :reject_if => :all_blank, :allow_destroy => true
end

class Dish < ActiveRecord::Base
  belongs_to :dish_category
end

Restaurant Form

<%= simple_form_for(@restaurant) do |f| %>
  <%= f.error_notification %>

  <div class="form-inputs">
    <%= f.input :name %>
  </div>

  <div class="form-inputs dish-category-inputs">
    <%= f.simple_fields_for :dish_categories do |f| %>
      <%= render 'dish_category_fields', f: f %>
    <% end %>
  </div>

  <div class='add-dish-category'>
    <%= link_to_add_association 'Add a Dish Category', f, :dish_categories %>
  </div>

  <div class="form-actions">
    <%= f.button :submit %>
  </div>
<% end %>

Dish Category Fields

<div class="dish-category-form nested-fields">
    <%= f.input :name %>

    <%= link_to_remove_association "Remove Category", f %>
</div>

Solution

  • So it looks like I did need to create an additional partial. From what I understand, the third parameter in the links_to_add_association expects a partial associated with the model for the form you wish to nest.

    Dish Category Fields

    <div class="dish-category-form nested-fields">
        <%= f.input :name %>
    
        <%= link_to_remove_association "Remove Category", f %>
    </div>
    
    <div class="form-inputs dish-category-inputs">
      <%= f.simple_fields_for :dishes do |f| %>
        <%= render "dish_fields", f: f %>
      <% end %>
    </div>
    
    <div class='add-dish'>
        <%= link_to_add_association "Add Dish", f, :dishes %>
    </div>
    

    Dish Fields

    <div class="dish-category-form nested-fields">
        <%= f.input :name %>
    
        <%= link_to_remove_association "Remove Dish", f %>
    </div>
    

    NOTE:

    When it comes to the restaurants controller code it may trip people up in regards to whitelisting your params. Its important that you place dishes_attributes within dish_categories_attributes in order for everything to save properly:

    Restaurants Controller

    class RestaurantsController < ApplicationController
      ...
      private
        def restaurant_params
          params.require(:restaurant).permit(:name, dish_categories_attributes: [:id, :restaurant_id, :name, :_destroy, dishes_attributes: [:id, :dish_category_id, :name, :description, :_destroy]])
        end
    end