Nested Forms in a Rails Application
In this blog, I will use a DIY organization app to demonstrate. **Although a user has an association with room through project, I will focus on the relationship between project and room.**
The setup: Fig. 1 — A project belongs_to a user and a room. This means the project table will contain foreign keys for users and rooms. A project can only be associated to one particular user and one particular room.
Fig. 2 — A room has_many projects. This means that in any particular room, that room can have many projects listed at once.
Fig. 3 — A user can have many projects and because rooms are associated to a project, the user also has access to many rooms through projects.
A nested form is a way for a has_many object to automatically be associated with its belongs_to counterpart upon saving to the database. When adding a nested form to your application, you will need to make changes in three locations — the controller, the model, and the view.
First there needs to be an understanding of what happens when the new method is called. A hash of arguments are being iterated over each key value pair through the send method, which are calling the setter method for the key and setting it equal to the attribute. When a nested form is accepted, it will be looking for a writer method to deal with that nested object and it’s information, in this case, will be room.
Fig.4 — In the new.html.erb file in the project folder, a form has already been created for a project. We will be selecting or creating a new room for a project with the nested form.
Fig. 5 — The setup for a nested form is just that, a form, nested inside of another form. The project form will iterate through each of the attributes. When it comes upon the room form, there is an attribute associated with room, which then will be iterated over to create a hash of its attributes. This is done through fields_for :room.
When setting up the nested form, we will need to make sure that a getter and setter method are created at the time of the form is submitted. If they are not present, the form will not submit and a MisMatchError will be thrown. We can set that up in the project model with the macro class method accepts_nested_attributes_for :object.
Fig. 6 — In the api.rubyonrails.org resource page, it lists this method which is pluralize or not, depending on the relationship it has.
Fig. 7 — If you want the nested form to not repeat itself upon submitting, a custom method can be created. Here it states that if any of the attributes for a room is not blank, search through the database to decide if that attribute has already been created and if not, create it.
At this point, if you were to submit the form, the nested part of the form would disappear. It all happens behind the scenes. When the form is submitted, and the fields_for line of code if read, it recognizes room as an association and it knows to look for the associated attributes. The problem at this time is the project has no associated room and because there is no association, nothing shows up when the form is submitted incorrectly. To make the association show up, we move to our controller next.
It’s the new action that’s loading the form. We need to make sure the project gets instantiated knowing about a blank copy of a room object.
Fig. 8 — The first line of code displays the project form. The second line of code associates the project to a blank copy of a room.
Fig.9 — What happens in the html code in the web console, is that project now has room_attributes as a key in its hash. If done correctly, it will always be the first object, the nested object_attributes, and then the attributes of the nested object.
Fig. 10 — Although everything looks like it’ll work properly, it’s not finished yet. In the terminal, you will see the form did not submit. This means room_attributes needs to be permitted in project params.
Fig. 11 — In the project controller, room_attributes needs to be added as a key pointing to an array of its own attributes, such as name. This is the last step to creating an associated object. Now the user doesn’t need to go to another page to create a new room to go along with a new project. They will have the ability to select from a drop down a room that has already been created or create a new room entirely.
If you’d like to see the whole code project, you can view it on github, here.