Handle nested fields_for by adding indexes to record_name

In case of the form with nested fields_for, i. e.

<%= form_for :foos, url: root_path do |f| %>
    <% @foos.each do |foo| %>
        <%= f.fields_for 'foo[]', foo do |f2| %>
            <%= f2.text_field :id %>
            <% foo.bars.each do |bar| %>
                <%= f2.fields_for 'bar[]', bar do |b| %>
                    <%= b.text_field :id %>
                <% end %>
            <% end %>
        <% end %>
     <% end %>
    <%= f.submit %>
<% end %>

rails doesn't add index for 'foo' in the inner fields_for block, so field names
in the outer fields_for looks like "foos[foo][#{foo_index}][id]" and in the
inner "foos[foo[]][bar][#{bar_index}][id]". Submitting of such form leads to an
error like:
>ActionController::BadRequest (Invalid request parameters: expected Array
>(got Rack::QueryParser::Params) for param `foo'):

This commit adds indexes for the foos in the inner blocks, so field names
become "foos[foo][#{foo_index}][bar][#{bar_index}][id]" and submitting of such
form works fine as expected.

Fixes #15332
This commit is contained in:
ojab 2015-08-30 10:02:24 +00:00
parent 79f44eb6c8
commit 7089768b70
2 changed files with 29 additions and 1 deletions

@ -1617,7 +1617,14 @@ def fields_for(record_name, record_object = nil, fields_options = {}, &block)
@auto_index
end
record_name = index ? "#{object_name}[#{index}][#{record_name}]" : "#{object_name}[#{record_name}]"
record_name = if index
"#{object_name}[#{index}][#{record_name}]"
elsif record_name.to_s.end_with?('[]')
record_name = record_name.to_s.sub(/(.*)\[\]$/, "[\\1][#{record_object.id}]")
"#{object_name}#{record_name}"
else
"#{object_name}[#{record_name}]"
end
fields_options[:child_index] = index
@template.fields_for(record_name, record_object, fields_options, &block)

@ -2292,6 +2292,27 @@ def test_nested_fields_for
assert_dom_equal expected, output_buffer
end
def test_deep_nested_fields_for
@comment.save
form_for(:posts) do |f|
f.fields_for('post[]', @post) do |f2|
f2.text_field(:id)
@post.comments.each do |comment|
concat f2.fields_for('comment[]', comment) { |c|
concat c.text_field(:name)
}
end
end
end
expected = whole_form do
"<input name='posts[post][0][comment][1][name]' type='text' id='posts_post_0_comment_1_name' value='comment #1' />"
end
assert_dom_equal expected, output_buffer
end
def test_nested_fields_for_with_nested_collections
form_for(@post, as: 'post[]') do |f|
concat f.text_field(:title)