Reputation: 3425
I have a Car model and an Brand model (each of which has_and_belongs_to_many). I am trying to create a new brand for a car. The car model accepts nested attributes for brand.
When I try
@car = Car.find(params[:car_id])
@brand = @car.brands.build(params[:brand])
I get a mass assignment error:
Can't mass-assign protected attributes: brands
Brands Controller:
class BrandsController < ApplicationController
def create
debugger
@car = Car.find(params[:car_id])
@brand= @car.brands.build(params[:brand])
if @brand.save
redirect_to @car
else
#do something
end
end
end
Calling the debugger with
pp params
Shows:
"brand"=>{"brands"=>{"name"=>"BMW"}},
"commit"=>"Create Brand",
"action"=>"create",
"controller"=>"brands",
"car_id"=>"4"}
The view looks like this:
<%= form_for([@car, @car.brands.build]) do |f| %>
<div class="field">
<%= f.fields_for :brands do |a| %>
<%= a.text_field :name, placeholder: "New Brand" %>
<% end %>
</div>
<p><%= f.submit %></p>
<% end %>
I want the application to be secure so I want to set the brand attributes individually as opposed to passing the whole "brand" hash (which incidentally does not work). How can I get the brand name? Something like
params[:brands].name?
Edit:
I can get the brand name with 'params[:brand][:brands][:name]'. Now it turns out that the brands table does not have a car_id since it is connected to a car with a join table.
Upvotes: 0
Views: 341
Reputation: 5617
Although having a model accept attributes for another is a useful tool in some cases, it often tends to be cumbersome and violates single responsibility principal not only at the model level, but also at the controller and view layer. Ultimately making it more difficult to refactor should your design need to change.
Keeping with the philosophy of REST, instead of creating Brands through Cars, use a BrandsController to handle the lifecycle of a Brand.
As for your requirement of security, setting a model's attributes manually does not provide any more security then passing the whole params hash assuming you are properly using attr_accessible. Giving it a whitelist of acceptable paramaters will tell the AR interface that everything else should be denied.
class Brand < ActiveRecord::Base
attr_accessible :name
end
So if a brand belongs to a car, and someone tried to change the relationship by hacking a car_id into the form it would reject car_id from the hash, possibly even raising an exception depending on your settings.
config.active_record.mass_assignment_sanitizer = :strict
That will cause a ActiveModel::MassAssignmentSecurity::Error
instead of just the Warning that is output into the logs.
Upvotes: 1
Reputation: 10769
<%= form_for @car do |f| %>
<div class="field">
<%= f.fields_for :brands do |a| %>
<%= a.text_field :name, placeholder: "New Brand" %>
<% end %>
</div>
<p><%= f.submit %></p>
<% end %>
In your model:
class Car < ActiveRecord::Base
has_many :brands
accepts_nested_attributes_for :brands
attr_accessible :brand_attributes
end
Upvotes: 1