Rails makes it easy to build HTML forms associated to a certain model. Simply using the form_for instruction on the view, writing a simple update method in the controller and setting validation logic on the model, makes standard CRUD operations incredibly easy to code.
Since version 2.3, Rails also provides a convenient way of dealing with multi-model forms, specifically parent-children relations. Ryan has an excellent blog post on the subject, I strongly recommend taking a look at it before going on reading if you have never used accepts_nested_attributes_for or fields_for.
To make a long story short, the accepts_nested_attributes_for method applied to a model allows you to assign values directly to the children, using the same hash format as for standard attributes.
An example
For example, having a Parent class which has_many children of class Child, a typical POST with data for both classes would look like the following:
params => action => update id => 1 controller => parents parent => first_name => John last_name => Doe age => 40 children_attributes => 1 => { id => 16, :name => Jack } 2 => { id => 18, :name => Mary }
(I removed several symbols from the hash to improve readability)
Note the children_attributes element among the parent’s attributes. The fields_for command produces the necessary syntax to produce POST data that will be converted to a hash in the convenient way for accepts_nested_attributes_for to interpret.
The children_attributes is not an array but a hash, and its keys are a simple indexer, not the IDs of the model, which is used to group attributes from a single child entity toghether.
The reason for not using IDs is simple once you think about it: the model being edited might not be saved by the time it is sent to the client (therefore its id is nil), so it is a good practice to keep a hidden field with the id for each of the children being displayed.
On the view
Taking a look at the page source generated, we can see the naming convention rails uses to generate a hash with the previous structure:
parent[children_attributes][0][name]
This naming is automatically generated by opening a new context using the fields_for instruction in the view.
On the model
The change in the model is really simple, as it involves simply telling Rails which classes might be updated directly from the parent:
accepts_nested_attributes_for :children
We will look into additional options (rejecting and deleting) later.
On the controller
Note that all these changes only involve the view (clearly) and a single line in the model to specify which classes may be modified through the attributes accessor. The controller remains as dumb as before:
def update if @parent.update_attributes params[:parent] redirect_to :action => 'success_page' else render :action => 'edit' end end
Not even a single mention to the children, the method is untouched.
After that…
Once you start dealing with certain validations and callbacks, keeping in mind when are these children validated created, updated and deleted is extemely important. Deleting items is specially delicate here, and will make a good subject for the next post!