Setting Up a Has Many / Has One Nested Association With Formtastic and Cocoon in Rails 4.2

September 20, 2015

Steps to Using a Double Nested Association with a hasmany, hasone nested association with Formtastic and Cocoon

Step 1 – The Models and Associations

I recently had to setup a double nested association in Rails with the formtastic and cocoon gems. Basically, a Person has_many Talks, and a Talk has one Location. I wanted to be able to add (or delete) a Talk with its corresponding Location.

Note: The source code for this repository is at this link: semanticfieldsfor_demo

Here is the model setup:

class Person < ActiveRecord::Base
  has_many :talks
  accepts_nested_attributes_for :talks

class Talk < ActiveRecord::Base
  has_one :location
  belongs_to :person
  accepts_nested_attributes_for :location

class Location < ActiveRecord::Base
  belongs_to :talk

Step 2 – The Controller Setup

I had a People controller with an edit_person_action. The important part is using the person_params to ensure you pass the correct whitelisted parameters.

def person_params
  params.require(:person).permit(:name, :email, talks_attributes: [:id, :person_id, :title, :\_destroy, location_attributes: [:id, :talk_id, :city, :state]] )

Note that the cocoon gem requires you to pass a __destroy flag in the attributes. Also notice that the location in location_attributes is singular since this is a has_one relationship.

The controller update action has a call to @person.update(personparams)_ once the forum is submitted with a new talk (or edited talk) and location.

Step 3 – The View Setup

Now there’s one special thing I do in the view that’s a bit hacky, but is necessary as I haven’t found a better solution.

Here is the _edit_person.html.haml view partial. This sets up the cocoon helpers so you can add fields.

= semantic_form_for @person do |f|

- if @person.errors.any?
  %h2= "#{pluralize(@person.errors.count, "error")} prohibited this person from being saved:"
  - @person.errors.full_messages.each do |msg|
      = msg
      = f.inputs do
        = f.input :name
        = f.input :email</code>

%h3 Talks


= f.semantic_fields_for :talks do |talk|

= render 'talk_fields', f: talk


= link_to_add_association 'Add Talk', f, :talks

= f.actions do

= f.submit 'Save'

Now, the _talk_fields.html.haml partial is where the special view trick happens.

= f.inputs do
= f.input :title

- f.object.build_location unless f.object.location
  = f.semantic_fields_for :location do |location|
  = render 'location_fields', f: location
  = link_to_remove_association "remove talk", f

Notice the f.object.build_location unless f.object.location line. Without this line you won’t be able to properly add/edit a location with a talk title. I think it’s possibly related to this issue in formtastic where has_one relationships only work with a workaround.

Note: The source code for this repository is at this link: semanticfieldsfor_demo