Steps to Using a Double Nested Association with a has_many, has_one 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: semantic_fields_for_demo
Here is the model setup:
class Person < ActiveRecord::Base
has_many :talks
accepts_nested_attributes_for :talks
end
class Talk < ActiveRecord::Base
has_one :location
belongs_to :person
accepts_nested_attributes_for :location
end
class Location < ActiveRecord::Base
belongs_to :talk
end
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]] )
end
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(person_params)_ 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?
#error_explanation
%h2= "#{pluralize(@person.errors.count, "error")} prohibited this person from being saved:"
%ul
- @person.errors.full_messages.each do |msg|
%li
= msg
= f.inputs do
= f.input :name
= f.input :email</code>
%h3 Talks
#talks
= f.semantic_fields_for :talks do |talk|
= render 'talk_fields', f: talk
.links
= 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.
.nested-fields
= 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: semantic_fields_for_demo