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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.