I have a form with a nested fields_for. I have attempted to permit the parameters that are generated by that nested form, but they are being blocked by strong parameters. I'm using rails 4.2.4 and ruby 2.2.2
I've read some official documentation:
I've read what looks like relevant SO posts:
I've read various blog posts:
I think I'm following what they say to do, but my nested attributes get rejected by strong parameters. I get things like Unpermitted parameters: __template_row__, 0, 1, 2, 3
in my log.
Here's my code:
models/enclosure.rb
class Enclosure < ActiveRecord::Base
has_many :resident_animals, -> { order("year DESC, month DESC") }, dependent: :restrict_with_error
validates :name, presence: true, uniqueness: {case_sensitive: false}
accepts_nested_attributes_for :resident_animals, allow_destroy: true, reject_if: :all_blank
def to_s
name
end
end
models/resident_animal.rb
class ResidentAnimal < ActiveRecord::Base
belongs_to :enclosure
validates_presence_of :enclosure, :year, :month, :color
...
end
controllers/enclosures_controller.rb
class EnclosuresController < ApplicationController
...
def update
@enclosure = Enclosure.find(params[:id])
@enclosure.update(enclosure_params)
respond_with @enclosure
end
private
def enclosure_params
params.require(:enclosure).permit(:name, :description, resident_animals_attributes: [:year, :month, :color, :id, :_destroy])
end
end
views/enclosures/_form.html.erb
<p class="field">
<%= form.label :name %>
<%= form.text_field :name %>
</p>
<p class="field">
<%= form.label :description %>
<%= form.text_area :description %>
</p>
<fieldset>
<legend>Resident Animals</legend>
<table id="resident-animal-rows">
<thead>
<th>Year <span class="required-field">*</span></th>
<th>Month <span class="required-field">*</span></th>
<th>Color <span class="required-field">*</span></th>
<th>Remove</th>
</thead>
<tbody>
<%= form.fields_for :resident_animals_attributes, ResidentAnimal.new(channel: form.object, year: Date.current.year, month: Date.current.month), index: "__template_row__" do |resident_animal_fields| %>
<tr class="resident-animal-row row-template">
<td><%= resident_animal_fields.number_field :year %></td>
<td><%= resident_animal_fields.select :month, month_options, include_blank: true %></td>
<td><%= resident_animal_fields.text_field :color %></td>
<td class="checkbox-cell"><%= resident_animal_fields.check_box :_destroy %></td>
</tr>
<% end %>
<%= form.fields_for :resident_animals do |resident_animal_fields| %>
<tr class="resident-animal-row">
<td><%= resident_animal_fields.number_field :year %></td>
<td><%= resident_animal_fields.select :month, month_options, include_blank: true %></td>
<td><%= resident_animal_fields.text_field :color %></td>
<td class="checkbox-cell">
<%= resident_animal_fields.hidden_field :id %>
<%= resident_animal_fields.check_box :_destroy %>
</td>
</tr>
<% end %>
</tbody>
</table>
<%= link_to "Add resident animal", "#", class: "resident-animal-row-add" %>
</fieldset>
When I log my parameters they look like:
{"enclosure"=>{"name"=>"Polar Quest", "description"=>"Polar bear attraction", "resident_animals_attributes"=>{"__template_row__"=>{"year"=>"2015", "month"=>"9", "color"=>"", "_destroy"=>"0"}, "0"=>{"year"=>"2005", "month"=>"8", "color"=>"white", "id"=>"1", "_destroy"=>"0"}, "1"=>{"year"=>"2012", "month"=>"7", "color"=>"yellow", "id"=>"2", "_destroy"=>"0"}, "2"=>{"year"=>"2011", "month"=>"3", "color"=>"white", "id"=>"4", "_destroy"=>"0"}, "3"=>{"year"=>"2006", "month"=>"2", "color"=>"yellowish", "id"=>"3", "_destroy"=>"0"}}}, "commit"=>"Update", "id"=>"1"}
Calling enclosure_params returns:
{"name"=>"Polar Quest", "description"=>"Polar bear attraction", "resident_animals_attributes"=>{}}
What am I doing wrong?
Thank you!
I'm going to add that correct answer from your comment here, just so this question can have a correct answer:
The problem is that .permit
-ing a nested hash that include IDs as the keys and a hash of other attributes as the values is a special case (as is apparent, since it doesn't match the typical argument structure for .permit
).
The trick is: the algorithm detects the special case (called fields_for_style? because it is the style of params typically submitted by the fields_for
helper) if, and only if, all of the keys translate to integers! Therefore, if you have a non-integer value (such as __template_row__
or new_record_id
) in the set of keys, it will not detect a special case, and instead reject every key in the hash that is not explicitly permitted (as for any typical hash).
In order to get around this, given the structure of params from the original post, you can simply remove the non-integer key and attributes submitted as part of the template row:
def enclosure_params
params[:enclosure][:resident_animals_attributes].delete(:__template_row__)
params.require(:enclosure).permit(...) # (no change from OP)
end
(Of course this means, you should be sure your interface does not try to submit meaningful data as part of your template row) ;)