How can I get strong parameters to permit nested fields_for attributes?

倾然丶 夕夏残阳落幕 提交于 2019-12-10 21:07:23

问题


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:

  • http://api.rubyonrails.org/classes/ActionController/StrongParameters.html
  • http://guides.rubyonrails.org/action_controller_overview.html#strong-parameters

I've read what looks like relevant SO posts:

  • Rails 4 Nested Attributes Unpermitted Parameters
  • How to permit strong params for nested attributes?

I've read various blog posts:

  • http://www.rubyexperiments.com/using-strong-parameters-with-nested-forms/
  • http://adamyonk.com/2013/05/16/rails-4-strong-parameters-and-nested-forms.html
  • and more

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?


回答1:


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) ;)




回答2:


def enclosure_params
  params.require(:).permit(:name, :description,
    resident_animals_attributes: [:enclosure_id, :year, :month, :color]
  )
end

May I suggest you use the newer format of rails validations:

validates: :enclosure_id, presence: true    
validates: :year, presence: true
validates: :month, presence: true
validates: :color, presence: true

You may need to use inverse: on the dependent model if you really need to require presence of enclosure on resident_animal. I'm not sure you need that validation.

The error is referring to this line

<%= 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| %>

in particular

index: "__template_row__" do |resident_animal_fields| 

You do not have an index attribute defined. Try removing that key value pair.



来源:https://stackoverflow.com/questions/32529757/how-can-i-get-strong-parameters-to-permit-nested-fields-for-attributes

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!