Getting the right HTML for Rails Nested Model Forms

Last week I had trouble getting Rails’s Nested Model Forms feature to work for me in exactly the way I wanted.  Two things were working against me: 1) No documentation that shows the complete process for nested models from ERB to HTML, to HTTP parameters, to controller code. and 2) Blogs and suggestions I came up with were close, but didn’t work.

Since I was using javascript rather than ERB to create the hidden input tags in the HTML, I needed to know exactly how to construct my tags to tell rails what to do with my nested models.

First a quick definition of Nested Model forms: These are forms where you manage an object and associated child objects within the same form.  For example, you might create a room (such as a hotel room) and it’s associated bookings in one form.  Another example closer to what I was trying to do is to update a room by adding and removing bookings within a single form.  The official Rails blog discusses it in some detail here.

So, rather than boring you with everything that didn’t work, allow me to get right to what works.

When using nested model forms, you can add new child objects to your model by ensuring your html form has properly named input tags.  Each input tag with a rather convoluted name (full of brackets like this: project[task_attributes][]) results in a new child object getting created with the name set to whatever is in the ‘value’ attribute of the html tag.

What I found odd is that delete didn’t seem to work the same way.  It turns out that in Rails 2.3.4 it is necessary to provide pairs of hidden input tags for each child you want to delete from your model.  This works because Rails figures out that it must group the two inputs together; one to define the id and the other to signify the delete.

Like this:

<input type="hidden" value="56" name="room[bookings_attributes][][id]"/>
<input type="hidden" value="1"  name="room[bookings_attributes][][_delete]"/>

This method raises an important concern, though.  Would this start failing if I had multiple children to delete in one server request?  Also what would happen if there was a mix of inserts and deletes?  Further testing showed odd behavior.  The only way I have been able to get this to work is with pairs of hidden inputs as follows:

<!-- Add a booking -->
<input type="hidden" name="room[bookings_attributes][][id]"/>
<input type="hidden" value="11/18/2009" name="room[bookings_attributes][][date]"/>

<!-- Delete a booking -->
<input type="hidden" value="15" name="room[bookings_attributes][][id]"/>
<input type="hidden" value="1" name="room[bookings_attributes][][_delete]"/>

<!-- Delete a booking -->
<input type="hidden" value="16" name="room[bookings_attributes][][id]"/>
<input type="hidden" value="1" name="room[bookings_attributes][][_delete]"/>

<!-- Add a booking -->
<input type="hidden" name="room[bookings_attributes][][id]"/>
<input type="hidden" value="01/22/2010" name="room[bookings_attributes][][date]"/>

These get translated to parameters as follows:

Parameters:
{"commit"=>"Update Reservation!","authenticity_token"=>"blah",
    "id"=>"1",
    "room"=> {"bookings_attributes"=>[{"id"=>"", "date"=>"11/18/2009"},
                                      {"_delete"=>"1", "id"=>"15"},
                                      {"_delete"=>"1", "id"=>"16"},
                                      {"id"=>"", "date"=>"01/22/2010"}
                                     ]
             }
}

I think that herein lies the key to why this works this way.  I wish I understood it a little better, but what I’m able to deduce is that the current implementation of nested attributes needs the child objects to be presented in an array where each element of the array is a hash containing child object attributes and optionally the delete flag.

Given that requirement, there seems to be a limitation on how the input tags are parsed.  The only way to get something on the right side of a ‘=>’ in the hashes is to put it in the ‘value’attribute of the html input tag.  For example:

<input type="hidden" value="1" name="room[bookings_attributes][16][_delete]"/>

gets parsed to

{"booking_attributes" => {"16" => {"_delete" => "1"}}}

The documentation says that should be ok, but Rails doesn’t actually do the deletion … nor does it complain.

So that’s a long and specific story, but it’s such an odd case and it doesn’t seem to be deeply explored in blogs and writeups, so I thought I’d share it with everyone.

Advertisements

4 Responses to “Getting the right HTML for Rails Nested Model Forms”

  1. Brenton Villarreal

    You have done it once again! Incredible read!

  2. Michael Harmer

    Thanks for making this note – it saved me no end of frustration. Once thing – in later versions on Rails you need _destroy rather than _delete to have child items removed.

  3. Janusch (@JanuschH)

    Hey,
    i am in a similar situation as you were in 2009. If you see my note, could you tell me if this is still an appropriate way of adding hidden form fields in Rails 4?
    Do we still need two fields for one attribute?

    My use case is that a user can search via elasticsearch and add nested models called “items” with various properties to a “product”, parent model. Both models get saved when the user clicks on submit.

    Nice read and good to know that you have done what I am about to do.

    Thanks and kind regards,
    Janusch

  4. Ashish Garg

    Thank you bhai

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s