Nested attributes unpermitted parameters

155,430

Solution 1

Seems there is a change in handling of attribute protection and now you must whitelist params in the controller (instead of attr_accessible in the model) because the former optional gem strong_parameters became part of the Rails Core.

This should look something like this:

class PeopleController < ActionController::Base
  def create
    Person.create(person_params)
  end

private
  def person_params
    params.require(:person).permit(:name, :age)
  end
end

So params.require(:model).permit(:fields) would be used

and for nested attributes something like

params.require(:person).permit(:name, :age, pets_attributes: [:id, :name, :category])

Some more details can be found in the Ruby edge API docs and strong_parameters on github or here

Solution 2

From the docs

To whitelist an entire hash of parameters, the permit! method can be used

params.require(:log_entry).permit!

Nested attributes are in the form of a hash. In my app, I have a Question.rb model accept nested attributes for an Answer.rb model (where the user creates answer choices for a question he creates). In the questions_controller, I do this

  def question_params

      params.require(:question).permit!

  end

Everything in the question hash is permitted, including the nested answer attributes. This also works if the nested attributes are in the form of an array.

Having said that, I wonder if there's a security concern with this approach because it basically permits anything that's inside the hash without specifying exactly what it is, which seems contrary to the purpose of strong parameters.

Solution 3

or you can simply use

def question_params

  params.require(:question).permit(team_ids: [])

end

Solution 4

Actually there is a way to just white-list all nested parameters.

params.require(:widget).permit(:name, :description).tap do |whitelisted|
  whitelisted[:position] = params[:widget][:position]
  whitelisted[:properties] = params[:widget][:properties]
end

This method has advantage over other solutions. It allows to permit deep-nested parameters.

While other solutions like:

params.require(:person).permit(:name, :age, pets_attributes: [:id, :name, :category])

Don't.


Source:

https://github.com/rails/rails/issues/9454#issuecomment-14167664

Solution 5

Today I came across this same issue, whilst working on rails 4, I was able to get it working by structuring my fields_for as:

<%= f.select :tag_ids, Tag.all.collect {|t| [t.name, t.id]}, {}, :multiple => true %>

Then in my controller I have my strong params as:

private
def post_params
    params.require(:post).permit(:id, :title, :content, :publish, tag_ids: [])
end

All works!

Share:
155,430
jcanipar
Author by

jcanipar

Updated on April 25, 2020

Comments

  • jcanipar
    jcanipar about 4 years

    I have a Bill object, which has many Due objects. The Due object also belongs to a Person. I want a form that can create the Bill and its children Dues all in one page. I am trying to create a form using nested attributes, similar to ones in this Railscast.

    Relevant code is listed below:

    due.rb

    class Due < ActiveRecord::Base
        belongs_to :person
        belongs_to :bill
    end
    

    bill.rb

    class Bill < ActiveRecord::Base
        has_many :dues, :dependent => :destroy 
        accepts_nested_attributes_for :dues, :allow_destroy => true
    end
    

    bills_controller.rb

      # GET /bills/new
      def new
          @bill = Bill.new
          3.times { @bill.dues.build }
      end
    

    bills/_form.html.erb

      <%= form_for(@bill) do |f| %>
        <div class="field">
            <%= f.label :company %><br />
            <%= f.text_field :company %>
        </div>
        <div class="field">
            <%= f.label :month %><br />
            <%= f.text_field :month %>
        </div>
        <div class="field">
            <%= f.label :year %><br />
            <%= f.number_field :year %>
        </div>
        <div class="actions">
            <%= f.submit %>
        </div>
        <%= f.fields_for :dues do |builder| %>
            <%= render 'due_fields', :f => builder %>
        <% end %>
      <% end %>
    

    bills/_due_fields.html.erb

    <div>
        <%= f.label :amount, "Amount" %>        
        <%= f.text_field :amount %>
        <br>
        <%= f.label :person_id, "Renter" %>
        <%= f.text_field :person_id %>
    </div>
    

    UPDATE to bills_controller.rb This works!

    def bill_params 
      params
      .require(:bill)
      .permit(:company, :month, :year, dues_attributes: [:amount, :person_id]) 
    end
    

    The proper fields are rendered on the page (albeit without a dropdown for Person yet) and submit is successful. However, none of the children dues are saved to the database, and an error is thrown in the server log:

    Unpermitted parameters: dues_attributes
    

    Just before the error, the log displays this:

    Started POST "/bills" for 127.0.0.1 at 2013-04-10 00:16:37 -0700
    Processing by BillsController#create as HTML<br>
    Parameters: {"utf8"=>"✓", 
    "authenticity_token"=>"ipxBOLOjx68fwvfmsMG3FecV/q/hPqUHsluBCPN2BeU=",
     "bill"=>{"company"=>"Comcast", "month"=>"April ", 
    "year"=>"2013", "dues_attributes"=>{
    "0"=>{"amount"=>"30", "person_id"=>"1"}, 
    "1"=>{"amount"=>"30", "person_id"=>"2"},
     "2"=>{"amount"=>"30", "person_id"=>"3"}}}, "commit"=>"Create Bill"}
    

    Has there been some change in Rails 4?

  • jcanipar
    jcanipar about 11 years
    I have changed my BillController to look like this: def bill_params params.require(:bill).permit(:company, :month, :year, :dues_attributes[:amount, :person_id]) end I am now getting this error: no implicit conversion of Symbol into Integer
  • jcanipar
    jcanipar about 11 years
    Well, it helps to put the colon in the right place... This is exactly what needed to be done. Thanks @thorsten-muller !
  • Arcolye
    Arcolye almost 11 years
    DON'T FORGET THE ID!!!! pets_attributes: [:id, :name, :category] Otherwise, when you edit, each pet will get created again.
  • Bartłomiej Skwira
    Bartłomiej Skwira over 10 years
    Awesome, I cound't get to permit a range parameter explicitly, this save me some hours.
  • 8bithero
    8bithero over 10 years
    Yeah, using .permit! is usually seen as a potential security concern. You'd only really want to use it if the user is an admin, but even then I'd be wary of its use.
  • andorov
    andorov over 10 years
    You need to do Person.create(person_params) or it won't call the method. Instead you'll get ActiveModel::ForbiddenAttributesError.
  • Clifton Labrum
    Clifton Labrum over 10 years
    My nested attributes are in an array, too. Is .permit! the only option? I can't get it to work even with all the model's attributes permitted because it chokes on the array.
  • Pathogen
    Pathogen over 9 years
    Also, if you want to destroy items from the form, you also need to whitelist the hidden :_destroy parameter. i.e. pets_attributes: [:id, :name, :category, :_destroy]
  • Hugo
    Hugo almost 9 years
    If you keep getting an error, make sure to add it at the end of the line (not in the middle). WRONG: :aaa, pets_attributes: [], :bbb. GOOD: :aaa, :bbb, pets_attributes: []
  • Chris Cirefice
    Chris Cirefice over 8 years
    @Arcolye I literally just dealt with this issue and I was very confused to see my records being duplicated after an UPDATE. What blows my mind is that Rails requires the :id, even in Rails 4, when this (in my opinion) should be an implicit parameter. Rails is great, but this to me doesn't make any damn sense.
  • BenKoshy
    BenKoshy almost 8 years
    will this work if you are submitting multiple pet objects?
  • BenKoshy
    BenKoshy almost 8 years
    hi thank you @KingsleyIjomah - what if you want to white list specific attributes of the children?