128

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?

sawa
  • 165,429
  • 45
  • 277
  • 381
jcanipar
  • 1,442
  • 2
  • 11
  • 8
  • 5
    Fix on formatting: params.require(:bill).permit(:company, :month, :year, :dues_attributes => [:amount, :person_id]) – Andy Copley Jul 15 '13 at 13:07

6 Answers6

193

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

Shiva
  • 11,485
  • 2
  • 67
  • 84
thorsten müller
  • 5,621
  • 1
  • 22
  • 30
  • 1
    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 Apr 10 '13 at 16:33
  • 2
    Well, it helps to put the colon in the right place... This is exactly what needed to be done. Thanks @thorsten-muller ! – jcanipar Apr 10 '13 at 21:32
  • 90
    DON'T FORGET THE ID!!!! `pets_attributes: [:id, :name, :category]` Otherwise, when you edit, each pet will get created again. – Arcolye Jun 03 '13 at 03:12
  • 8
    You need to do `Person.create(person_params)` or it won't call the method. Instead you'll get `ActiveModel::ForbiddenAttributesError`. – andorov Sep 25 '13 at 05:04
  • 17
    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]` – Pathogen Aug 08 '14 at 18:42
  • 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: [] – Hugo Jun 23 '15 at 00:54
  • @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. – Chris Cirefice Aug 07 '15 at 17:01
  • will this work if you are submitting multiple pet objects? – BenKoshy Jul 11 '16 at 23:42
23

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.

Leahcim
  • 40,649
  • 59
  • 195
  • 334
  • Awesome, I cound't get to permit a range parameter explicitly, this save me some hours. – Bartłomiej Skwira Sep 06 '13 at 13:09
  • 3
    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. – 8bithero Sep 23 '13 at 05:22
  • 7
    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. – Clifton Labrum Dec 10 '13 at 00:09
20

or you can simply use

def question_params

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

end
Surya
  • 15,703
  • 3
  • 51
  • 74
Amit Agarwal
  • 396
  • 3
  • 12
13

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

nothing-special-here
  • 11,230
  • 13
  • 64
  • 94
3

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!

Kingsley Ijomah
  • 3,273
  • 33
  • 25
1

If you use a JSONB field, you must convert it to JSON with .to_json (ROR)

Wouter Schoofs
  • 966
  • 9
  • 13