mass_assignment_authorizer and nested attributes
I'm using dynamic attr_accessible
as per this article:
http://asciicasts.com/episodes/237-dynamic-attr-accessible
It works fine. But I haven't found an elegant way to make it work with nested attributes. Here's some simplified code:
class Company < ActiveRecord::Base
has_many :employees
accepts_nested_attributes_for :employees
end
class Employee < ActiveRecord::Base
belongs_to :company
attr_protected :salary
attr_accessor :accessible
def mass_assignment_authorizer
if accessible == :all
ActiveModel::MassAssignmentSecurity::BlackList.new
else
super + (accessible || [])
end
end
end
Let's say I have an admin interface with a RESTful form for a Company. On this form, I have fields for employees_attributes
, including blank fields to create new Employees. I can't find a way to call Employee#accessible=
in this context. Browsing through the ActiveRecord source code, it seems that this might be impossible: in the remotest part of a very deep call stack, nested associations just result in Employee.new
being called with the attributes.
I'd thought about creating a special attribute that could be passed in through mass assignment. If the attribute's value were the right code, the Employee instance would set @accessible
to :all
. But I don't think there's a way to guar开发者_如何转开发antee that this attribute gets set before the protected attributes.
Is there any way to make dynamic protected attributes work with nested attributes?
I'm new to rails, and have had boatloads of trouble trying to get nested attributes to work myself, but I found that I had to add the nested attributes to my accessible list.
class Company < ActiveRecord::Base
has_many :employees
accepts_nested_attributes_for :employees
attr_accessible :employees_attributes
end
My understanding is that accepts_nested_attributes_for
creates that special employees_attributes
, but when you default all attributes to non-accessible (which I believe the asciicast does) you won't be able to use it.
I hope that helps.
This seems to me like something that could be set directly from your controller code on the class for this request. E.g.
Employee.accessible = :all
Company.create(params[:company])
Employee.accessible = nil
Which could be extracted to a block like
def with_accessible(*types)
types.flatten!
types.each{|type| type.accessible = :all}
yield
types.each{|type| type.accessible = nil}
end
So your final controller code is
with_accessible(Employee, OtherClass, YetAnotherClass) do
Company.create(params[:company])
end
Pretty expressive of what's going on for the case of all attributes
For the case of only certain attributes, I might modify it to the following
def with_accessible(*types, &block)
types.flatten!
return with_accessible_hash(types.first, &block) if types.first.is_a?(Hash)
types.each{|type| type.accessible = :all}
ret = yield
types.each{|type| type.accessible = nil}
ret
end
def with_accessible_hash(hash, &block)
hash.each_pair do |klass, accessible|
Object.const_get(klass).accessible = accessible
end
ret = yield
hash.keys.each{|type| type.accessible = nil}
ret
end
Which gives you
with_accessible(:Employee => [:a, :b, :c], :OtherClass => [:a, :b]) do
Company.create(params[:company])
end
精彩评论