Search code examples
ruby-on-rails-3fields-for

How to get correct children ids using fields_for "parents[]", parent do |f| using f.fields_for :children, child?


I'm editing multiple instances of a parent model in an index view in one form, as in Railscasts #198. Each parent has_many :children and accepts_nested_attributes_for :children, as in Railscasts #196 and #197

<%= form_tag %>
  <% for parent in @parents %>
    <%= fields_for "parents[]", parent do |f|
      <%= f.text_field :job %>
      <%= f.fields_for :children do |cf| %>
         <% cf.text_field :chore %>
      <% end %> 
    <% end %> 
  <% end %> 
<% end %>  

Given parent.id==1
f.text_field :job correctly generates

<input id="parents_1_job" type="text" value="coding" size="30" name="parents[1][job]">  

But cf.text_field :chore generates ids and names that don't have the parent index.

id="parents_children_attributes_0_chore"  
name="parents[children_attributes][0][chore]"    

If I try passing the specific child object to f.fields_for like this:

<% for child in parent.children %>
  <%= f.fields_for :children, child do |cf| %>
    <%= cf.text_field :chore %>
  <% end %>
<% end %>  

I get the same. If I change the method from :children to "[]children" I get

id="parents_1___children_chore"

which gets the right parent_index but doesn't provide an array slot for the child index.

"[]children[]" isn't right either: id="parents_1__children_3_chore"

as I was expecting attributes_0_chore instead of 3_chore.

Do I need to directly modify an attribute of the FormBuilder object, or subclass FormBuilder to make this work, or is there a syntax that fits this situation?

Thanks for any thoughts.


Solution

  • I did solve this problem by reading the source code for FormBuilder.fields_for

    One possible answer: Yes, modify the f.object_name attribute of the FormBuilder object.

    Specifically in this situation

    f.fields_for :children  
    

    is going to call

    f.fields_for_with_nested_attributes
    

    which sets the name variable based on f.object_name. The id for the generated element looks like it is based on the name,so both match in the resulting html.

    def fields_for_with_nested_attributes(association_name, args, block)
          name = "#{object_name}[#{association_name}_attributes]"
    .....
    

    So one way to tell f.fields_for to do what I wanted is to set f.object_name to include the parent id for the duration of the f.fields_for block

    <% old_object_name = f.object_name %>
    <% f.object_name="parents[#{f.object.id}]" %>
    <% =f.fields_for :children do |cf| %>
      <%= cf.text_field :chore %>
    <% end %>
    <% f.object_name=old_object_name #should be "parents[]" %>
    

    Then everything within the f.fields_for block can use the standard rails helpers unmodified.