127

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?

1
  • 5
    Fix on formatting: params.require(:bill).permit(:company, :month, :year, :dues_attributes => [:amount, :person_id]) Commented Jul 15, 2013 at 13:07

6 Answers 6

192

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

Sign up to request clarification or add additional context in comments.

8 Comments

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
Well, it helps to put the colon in the right place... This is exactly what needed to be done. Thanks @thorsten-muller !
DON'T FORGET THE ID!!!! pets_attributes: [:id, :name, :category] Otherwise, when you edit, each pet will get created again.
You need to do Person.create(person_params) or it won't call the method. Instead you'll get ActiveModel::ForbiddenAttributesError.
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]
|
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.

3 Comments

Awesome, I cound't get to permit a range parameter explicitly, this save me some hours.
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.
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.
20

or you can simply use

def question_params params.require(:question).permit(team_ids: []) end 

Comments

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

Comments

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!

1 Comment

hi thank you @KingsleyIjomah - what if you want to white list specific attributes of the children?
1

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.