Sent from my Verizon Wireless BlackBerry
FWIW, Sequel has had something similar to what you propose for about a
year. You can set accessible/protected attributes at a class level
and you can use the set_(only|except) instance methods to only
allow/reject specific attributes per method call. I added support
for it for pretty much the same reasons you gave. The all-or-nothing
approach of ActiveRecord's mass assignment is not flexible enough to
handle different security contexts.
I'm -1 on this being added to ActiveRecord, so Sequel keeps its
advantage in this area. ;p
Jeremy
I completely agree with you. Which attributes are allowed to be
mass-assigned is almost always context-sensitive, and thus the
whitelist doesn't belong in the class level.
> And yet ... it's such a pretty syntax. Who wants to give that up?
>
> I do! Well, just a little.
>
> My idea (most likely not original) is to let the controller specify which
> attributes may be assigned. In its briefest form, my proposal is be to
> remove attr_accessible/attr_protected and change the mass assignment API as
> follows:
>
> * User.new(params[:user]) becomes User.new(params[:user], whitelist)
> * @user.attributes = params[:user] becomes @user.assign(params[:user],
> whitelist)
Agreed.
How about a simple extension to Hash (somebody let me know if this
exists already) that allows you to do:
user.attributes = params[:user].pick(:first_name, :last_name)
(Naive implementation here: http://gist.github.com/116880)
The RHS could be extracted to a separate method in the relevant
controller, which would do the context checking.
Regardless of the syntax I think the use of attr_accessible at the
class level should not be encouraged. I'm +1 on this.
> How about a simple extension to Hash (somebody let me know if this
> exists already) that allows you to do:
>
> user.attributes = params[:user].pick(:first_name, :last_name)
This is what active support #slice and #slice! hash extensions do and
I have used this in my controllers already for mass attribute white
lists per controller action "when needed" for a few months now. I
think since 2.3.2 (perhaps earlier) the #slice method even do as
expected for indifferent access hashes.
>> {'a' => 'fff', 'b' => 'ccc'}.with_indifferent_access.slice(:a)
=> {"a"=>"fff"}
- Ken
--Matt Jones
I don't think this massive change to the api is justified. You're
introducing complexity for all users to support a few cases which, while
hardly rare, aren't 100% of user's requirements.
It should be trivial for you to implement this as a plugin to see if
people prefer this approach to specifying assignable attributes. If
that picks up momentum we can look at pulling it in to rails.
In the meantime users can already do:
@user.attributes = params[:user].slice(:email, :password,
:password_confirmation)
or
@user.attributes = params[:user].except(:admin)
> I'd really prefer to remove attr_accessible/attr_protected altogether
> as I believe they are in all ways inferior to the new approach and
> would only serve to clutter the API in the name of backwards
> compatibility. But that's a secondary concern, and will be in a second
> ticket that may be evaluated independently.
attr_accessible and friends are a great simple solution for a really
common case. We shouldn't lose sight of that just because there are
some cases where they're not perfect.
--
Cheers,
Koz
I'm still struggling a bit for clear reasons why it might be better to
filter the attributes controller-side or model-side.
> I agree. Lance raises very valid points, but what would what's wrong
> with using Hash#slice and Hash#except to solve these problems?
> Especially if the documentation for attr_accessible/attr_protected can
> be updated to refer to the usage of Hash#slice and Hash#except for
> more complex use cases?
Yeah I've added it
http://github.com/lifo/docrails/commit/6197606588674bd16e17899e0df15adf2a482ba0
I think a concrete application will probably need some controller
macro to be able to express those tweaks at a higher level. But the
basic technique to build upon is documented there anyway.
@model.update_attributes(params[:whatever],
[:stuff_non_admins_cant_change])
So essentially a, "no, really, you can mass-assign these attributes
just this once" parameter. That would still allow regular code to work
correctly while permitting the context-sensitive stuff you're looking
for.
--Matt Jones
Certainly the Hash#except idiom requires you whitelist (or
not-blacklist) sensitive data, because attr_(accessible|protected) are
of course applied to whatever sanitized hash you pass. So in
particular you can only narrow accessible aattributes (or extend
protected attributes)
Going the other way around sounds better to me, not sure about the API
though. I think it requires the same amount of configuration and
exceptions, but looks like a safer default.
I implemented all the changes mentioned in my last email, only
#assign_attributes is pending. http://bit.ly/1wSmS
Few considerations:
* I implemented #protect_from_mass_assignment at class level, instead of
a top-level ActiveRecord flag, because gives a fine grained control on
the protection.
* Associations attribute writers should be explicitly declared as
accessible.
* #accepts_nested_attributes_for, automatically adds the attribute to
the accessible list
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts
end
Member.accessible_attributes # => [ 'posts_attributes' ]
Cheers,
Luca
--
blog: www.lucaguidi.com
> The form fields I specify in the form are the only fields the user is
> allowed to change on that particular entry point. Why dont we take
> this given as leading and mould our controllers and models to this set
> of allowed fields?
Imagine a multi-account invoicing application that has a customer
selector in the form for invoice creation.
That customer_id has to be protected to prevent users from injecting
customer_ids belonging to other accounts. Yet it belongs to the form.