Topic: fields_for, tables and the hidden id field

Hey guys.

Suppose I have a collection of objects that I'm iterating over, and I'm wanting the user to enter data for the fields. Suppose I'm also outputting the fields in to a table.

Here's the code I'm using:

      <% f.fields_for :things do |things_form| %>
      <tr class="<%= cycle("odd","even") %>">
        <td>
          <%= things_form.text_field :description, :size => 30 %>
        </td>
        <td>
          <%= things_form.text_field :value %>
        </td>
      </tr>
    <% end %>

Now, because fields_for automatically spits out the hidden input field for the ID, this is the resultant HTML (with the surrounding table included):

<table cellspacing="1" cellpadding="2">
    <thead>
    <tr>
      <th>Description</th>
      <th>Current value</th>
    </tr>
    </thead>

    <tbody>

      <input id="application_thing_attributes_0_id" name="application[thing_attributes][0][id]" type="hidden" value="36912">
      <tr>
        <td>
          <input id="application_thing_attributes_0_description" maxlength="255" name="application[thing_attributes][0][description]" value="Blah">
        </td>
        <td>
          <input id="application_thing_attributes_0_asset_value" maxlength="15" name="application[thing_attributes][0][asset_value]" size="10" type="text" value="400">
        </td>
      </tr>

      <input id="application_thing_attributes_1_id" name="application[thing_attributes][1][id]" type="hidden" value="36913">
      <tr>
        <td>
          <input id="application_thing_attributes_1_description" maxlength="255" name="application[thing_attributes][1][description]" size="30" type="text" value="">
        </td>
        <td>
          <input id="application_thing_attributes_1_asset_value" maxlength="15" name="application[thing_attributes][1][asset_value]" size="10" type="text" value="0">
        </td>
      </tr>

    </tbody>
</table>

If you look closely, the HTML is invalid because there is an <input> element nested underneath <tbody>.

How can I explicitly set where the ID field is rendered so that the fields_for loop will render valid HTML?

Re: fields_for, tables and the hidden id field

So I left this alone for a while and came back to the problem recently.  After a little scrutiny on how our Rails application was outputting forms, it turned out that there was a custom initializer that was overriding the default FormBuilder for Rails. The overridden implementation was spitting out the hidden ID fields before the template was even parsed, which is why I was seeing the ID fields above the table rows.

Re: fields_for, tables and the hidden id field

OK, so it turns out that Rails 2.3.5 makes this pretty dead simple:

<% f.fields_for :things do |things_form| %>
      <tr class="<%= cycle("odd","even") %>">
        <td>
          <%= things_form.hidden_field :id %>
          <%= things_form.text_field :description, :size => 30 %>
        </td>
        <td>
          <%= things_form.text_field :value %>
        </td>
      </tr>
    <% end %>

When you explicitly place the hidden ID field, Rails will oblige and insert the ID field for you at the location you specify.

<table cellspacing="1" cellpadding="2">
    <thead>
    <tr>
      <th>Description</th>
      <th>Current value</th>
    </tr>
    </thead>

    <tbody>

      <tr>
        <td>
          <input id="application_thing_attributes_0_id" name="application[thing_attributes][0][id]" type="hidden" value="36912">
          <input id="application_thing_attributes_0_description" maxlength="255" name="application[thing_attributes][0][description]" value="Blah">
        </td>
        <td>
          <input id="application_thing_attributes_0_asset_value" maxlength="15" name="application[thing_attributes][0][asset_value]" size="10" type="text" value="400">
        </td>
      </tr>

      <tr>
        <td>
          <input id="application_thing_attributes_1_id" name="application[thing_attributes][1][id]" type="hidden" value="36913">
          <input id="application_thing_attributes_1_description" maxlength="255" name="application[thing_attributes][1][description]" size="30" type="text" value="">
        </td>
        <td>
          <input id="application_thing_attributes_1_asset_value" maxlength="15" name="application[thing_attributes][1][asset_value]" size="10" type="text" value="0">
        </td>
      </tr>

    </tbody>
</table>

Re: fields_for, tables and the hidden id field

For those interested, here is the relevant Rails method that governs the behaviour of generating these nested fields:

ActionView::Helpers::FormBuilder#fields_for_nested_model

You will notice that the template is run on the block passed to it (ie: your ERB code) first, and once that's done it emits a hidden ID field. However, it only emits that ID field if it isn't a new record, and hasn't already been done manually.

The associated methods that lets Rails figure out if it needs to create a hidden ID field are:

ActionView::Helpers::FormBuilder#emitted_hidden_id?
ActionView::Helpers::FormBuilder#hidden_field

Last edited by Dazza (2010-08-10 20:41:30)

Re: fields_for, tables and the hidden id field

Thank you for solving it!