Reputation: 378
An article called Triple nested Forms in Rails presents a good description of creating a form for saving three nested objects. The example given is of creating a Show
that has_many Seasons
, and each Season
has_many Episodes
. Also, Episode --> belongs_to --> Season --> belongs_to --> Show
.
Shows are created like this:
def new
@show = Show.new
@show.seasons.build.episodes.build
end
The form looks like this:
<%= form.fields_for :seasons do |s| %>
<%= s.label :number %>
<%= s.number_field :number %> <%= s.fields_for :episodes do |e| %>
<%= e.label :title %>
<%= e.text_field :title %>
<% end %>
<% end %>
<% end %>
This seems straightforward because all the associations run in one direction. I'm trying to do something that's similar, but more complicated. I have a Parent
model where each Parent
has multiple Children
and each Child
is enrolled in a School
. After specifying that Children
is the plural of Child
, the association would have to be like this:
Parent has_many Children, accepts_nested_attributes_for :children
Child belongs_to Parent, belongs_to School, accepts_nested_attributes_for :school
School has_many Children, accepts_nested_attributes_for :children
Graphically, it would look like this:
Parent <-- belongs_to <-- Child --> belongs_to --> School
Each Parent is also associated with a User, like this:
User has_many :parents
The data on Parents, Children, and Schools is entered in the following form (generated using the Simple Form gem), where the schools are entered as a dropdown selector populated from the schools table:
@schools = School.all
<%= simple_form_for (@parent) do |f| %>
<%= f.input :name, label: 'name' %>
<%= f.simple_fields_for :children, @children do |child_form| %>
<%= child_form.input :name, label: "Child Name" %>
<%= child_form.simple_fields_for :school, @school do |school %>
<%= school.collection_select :id, @schools, :id, :name, {}, {} %>
<% end %>
<% end %>
<% end %>
I set up the new
controller method to create a Parent
having three Children
enrolled in an existing School
. Then I tried to associate the Children
with a School
that already exists in the schools
table with id = 1.
def new
@parent = Parent.new
# creating 3 children
@children = Array.new(3) {@parent.children.build}
@school = School.find(1)
@school.children.build
end
This throws an error
Couldn't find School with ID=1 for Child with ID=
The error is located in the first line of the create method, which looks like this:
def create
@parent = Parent.new(parent_params.merge(:user => current_user))
if @parent.save
redirect_to root_path
else
render :new, :status => :unprocessable_entity
end
end
def parent_params
params.require(:parent).permit(:name, :child_attributes => [:id, :name, age, :school_attributes => [:id, :name]])
end
Since the error text states "Child with ID= "
, the error must be thrown before ids for new Children
are assigned. Why can a School
with ID=1 not be found when it exists in the schools
table? Or, does this mean that a School
record has not been properly associated with an instance of Child
before an attempt is made to save that instance? If so, how can I fix the association?
Upvotes: 0
Views: 800
Reputation: 102378
One of the most common missconceptions/misstakes with nested attributes is to think that you need it to do simple association assignment. You don't. You just need to pass an id to the assocation_name_id=
setter.
If fact using nested attributes won't even do what you want at all. It won't create an assocation from an existing record when you do child.school_attributes = [{ id: 1 }]
rather it will attempt to create a new record or update an existing school record.
You would only need to accept nested attributes for school if the user is creating a school at the same time. And in that case its probally a better idea to use Ajax rather than stuffing everything into one mega action.
<%= simple_form_for (@parent) do |f| %>
<%= f.input :name, label: 'name' %>
<%= f.simple_fields_for :children, @children do |child_form| %>
<%= child_form.input :name, label: "Child Name" %>
<%= child_form.associaton :school,
collection: @schools, label_method: :name %>
<% end %>
<% end %>
def parent_params
params.require(:parent).permit( :name,
child_attributes: [:id, :name, :age, :school_id]]
)
end
Upvotes: 2