# redesigning mass attribute assignment (activerecord 3.0?)

**URL:** <https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775>\
**Category:** rubyonrails-core\
**Created:** [May 23, 2009, 2:42am UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775 "2009-05-23T02:42:03Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Lance](https://avatars.discourse-cdn.com/v4/letter/l/7ab992/32.png) [@Lance](https://discuss.rubyonrails.org/u/Lance)\
**Post date:** [May 23, 2009, 2:42am UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775/1 "2009-05-23T02:42:03Z")

</div>

Hello!

I’d like to refactor the code for mass assignment to support a new security paradigm, but figured that I might save some time getting feedback from the gatekeepers of the code. 🙂

We’re all familiar with attr\_accessible and attr\_protected. These methods provide rudimentary support from malicious mass assignment … when people remember to use them. I usually remember to set them myself, except that I really wouldn’t be surprised if someone auditing my code found one or two spots that I’ve forgotten. But this isn’t about just changing the default to a hardcore blacklist instead of an all-encompassing whitelist. This is about when attr\_accessible and attr\_protected aren’t enough.

They’re not enough when user roles enter into the picture. Just to take the simplest possible example, let’s suppose that an application has normal registered users and it has staff. The staff, very trustable sorts (crossed fingers), are allowed to change attributes that normal users are not. It’s a problem that attr\_accessible and attr\_protected, being very static lists, are not prepared to handle. But let’s not stop there. We’ve all coded spots where we wanted to directly assign a couple of attributes ourselves to trusted values, but we couldn’t use mass assignment because we (the devs) have essentially told the code to not even trust ourselves.

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)

Overall I think that this makes things a whole lot cleaner! Which may or may not be the same thing as fewer LOC. Consider:

- The whitelist would be optional, and if absent, would default to most every column. Which makes it usable for normal coding, and doesn’t get in the way when you’re just getting started.
- The whitelist makes it very obvious how the code works. Obvious is better than hidden. I normally don’t realize that my attr\_accessible isn’t configured right until I happen to scan logs and see that “can’t mass assign” message.
- The whitelist can be a nested hash of any depth, offering excellent support for the nascent nested forms functionality.
- The whitelist can be different in a controller that enforces broader security and requires admin users in the first place.
- It moves security where it belongs, into the context of a user. Security is business logic, in my mind, and I do like that in my (still-skinny) controllers.
- The whitelist can be generated on-demand according to the current user. Which would offer an actual extension point for permission-management plugins and their User.current class/thread variable hacks!
- I like the assign() syntax better than the attributes= syntax. But that could be just me.

I think that Rails 3.0 would be the right time for an API change like this. It would provide a much better extension point for permission management plugins, and, well, there are no holy cows, right? So … should I put in the time and code it up? What do you all think?

-Lance

---

<div class="post-metadata">

**Author:** ![Kenneth\_Ortmann](https://avatars.discourse-cdn.com/v4/letter/k/ea5d25/32.png) [@Kenneth\_Ortmann](https://discuss.rubyonrails.org/u/Kenneth_Ortmann)\
**Post date:** [May 23, 2009, 2:48am UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775/2 "2009-05-23T02:48:35Z")

</div>

I like it.

---

<div class="post-metadata">

**Author:** ![Jeremy\_Evans](https://avatars.discourse-cdn.com/v4/letter/j/97f17d/32.png) [@Jeremy\_Evans](https://discuss.rubyonrails.org/u/Jeremy_Evans)\
**Post date:** [May 23, 2009, 4:03am UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775/3 "2009-05-23T04:03:08Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![Damian\_Janowski](https://avatars.discourse-cdn.com/v4/letter/d/f6c823/32.png) [@Damian\_Janowski](https://discuss.rubyonrails.org/u/Damian_Janowski)\
**Post date:** [May 23, 2009, 11:40pm UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775/4 "2009-05-23T23:40:50Z")

</div>

> We're all familiar with attr\_accessible and attr\_protected. These methods provide rudimentary support from malicious mass assignment ... when people remember to use them. I usually remember to set them myself, except that I really wouldn't be surprised if someone auditing my code found one or two spots that I've forgotten. But this isn't about just changing the default to a hardcore blacklist instead of an all-encompassing whitelist. This is about when attr\_accessible and attr\_protected aren't enough.
> 
> They're not enough when user roles enter into the picture. Just to take the simplest possible example, let's suppose that an application has normal registered users and it has staff. The staff, very trustable sorts (crossed fingers), are allowed to change attributes that normal users are not. It's a problem that attr\_accessible and attr\_protected, being very static lists, are not prepared to handle. But let's not stop there. We've all coded spots where we wanted to directly assign a couple of attributes ourselves to trusted values, but we couldn't use mass assignment because we (the devs) have essentially told the code to not even trust ourselves.

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: [gist:116880 · GitHub](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.

---

<div class="post-metadata">

**Author:** ![Ken\_Collins1](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/ken_collins1/32/778_2.png) [@Ken\_Collins1](https://discuss.rubyonrails.org/u/Ken_Collins1)\
**Post date:** [May 24, 2009, 12:16am UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775/5 "2009-05-24T00:16:40Z")

</div>

> 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"}

&nbsp;&nbsp;- Ken

---

<div class="post-metadata">

**Author:** ![Lance](https://avatars.discourse-cdn.com/v4/letter/l/7ab992/32.png) [@Lance](https://discuss.rubyonrails.org/u/Lance)\
**Post date:** [May 24, 2009, 12:40am UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775/6 "2009-05-24T00:40:02Z")

</div>

Which points to an interesting question -- should the model or the controller be responsible for filtering the attributes? That is, should the burden be on the model to only assign allowed parameters, or the controller to only pass allowed parameters? It certainly seems simple to do it from the controller using something like your Hash#pick method, but I think it's safer to do it from the model. For example, if the model is responsible for filtering assignable attributes, it may create an intelligent default blacklist for cases where the developer has paid no attention.

I've just about finished a patch to implement AR::Base#assign (attributes, allowed\_attributes). In the process I've realized that allowed\_attributes can simply be an override to attr\_accessible/ attr\_protected, which makes for an easily backwards compatible API update. So that'll be my first ticket.

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.

---

<div class="post-metadata">

**Author:** ![Lance](https://avatars.discourse-cdn.com/v4/letter/l/7ab992/32.png) [@Lance](https://discuss.rubyonrails.org/u/Lance)\
**Post date:** [May 24, 2009, 4:16am UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775/7 "2009-05-24T04:16:16Z")

</div>

Ok, primary ticket: [https://rails.lighthouseapp.com/projects/8994/tickets/2704-allow-calltime-list-of-allowed-attributes-for-mass-assignment](https://rails.lighthouseapp.com/projects/8994/tickets/2704-allow-calltime-list-of-allowed-attributes-for-mass-assignment)

And the bonus round: [https://rails.lighthouseapp.com/projects/8994/tickets/2705-deprecate-attr\_accessible-attr\_protected](https://rails.lighthouseapp.com/projects/8994/tickets/2705-deprecate-attr_accessible-attr_protected)

---

<div class="post-metadata">

**Author:** ![Matt\_Jones](https://avatars.discourse-cdn.com/v4/letter/m/258eb7/32.png) [@Matt\_Jones](https://discuss.rubyonrails.org/u/Matt_Jones)\
**Post date:** [May 24, 2009, 5:05am UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775/8 "2009-05-24T05:05:41Z")

</div>

The second one seems like a really, really bad idea. Specifying the allowed attributes at the call is great, but what about the simpler use cases? Sometimes you just want to prohibit (or allow) mass assignment, and requiring users to specify the list every time is the opposite of DRY. Not to mention the maintenance headache when adding a new field...

--Matt Jones

---

<div class="post-metadata">

**Author:** ![Michael\_Koziarski1](https://avatars.discourse-cdn.com/v4/letter/m/aca169/32.png) [@Michael\_Koziarski1](https://discuss.rubyonrails.org/u/Michael_Koziarski1)\
**Post date:** [May 24, 2009, 6:25am UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775/9 "2009-05-24T06:25:44Z")

</div>

cainlevy wrote:

> Which points to an interesting question -- should the model or the controller be responsible for filtering the attributes? That is, should the burden be on the model to only assign allowed parameters, or the controller to only pass allowed parameters? It certainly seems simple to do it from the controller using something like your Hash#pick method, but I think it's safer to do it from the model. For example, if the model is responsible for filtering assignable attributes, it may create an intelligent default blacklist for cases where the developer has paid no attention.
> 
> I've just about finished a patch to implement AR::Base#assign (attributes, allowed\_attributes). In the process I've realized that allowed\_attributes can simply be an override to attr\_accessible/ attr\_protected, which makes for an easily backwards compatible API update. So that'll be my first ticket.

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.

---

<div class="post-metadata">

**Author:** ![Lance](https://avatars.discourse-cdn.com/v4/letter/l/7ab992/32.png) [@Lance](https://discuss.rubyonrails.org/u/Lance)\
**Post date:** [May 24, 2009, 8:33am UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775/10 "2009-05-24T08:33:26Z")

</div>

Hey Matt, I did some thinkng about this and I'm not sure if the simpler use cases are actually simpler with attr\_accessible, or if they're just different? For any given model I generally only have one resource controller, so in that case I'd simply be moving the attribute list from the model to the controller. And when a model is represented by more than one resource controller, it's probably because of an entirely different context with different permissions. And of course there are easy ways to DRY up a list of attributes that are commonly used without needing official API support.

Do you have any examples where this would be a bad idea? Perhaps I'm missing something obvious here.

But yes, I split #2705 into a separate ticket because it's more about opinion than functionality.

---

<div class="post-metadata">

**Author:** ![Lance](https://avatars.discourse-cdn.com/v4/letter/l/7ab992/32.png) [@Lance](https://discuss.rubyonrails.org/u/Lance)\
**Post date:** [May 25, 2009, 11:36pm UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775/11 "2009-05-25T23:36:52Z")

</div>

I've put the core assign() method into a plugin. Feedback is always desired.

[http://github.com/cainlevy/mass\_assignment](http://github.com/cainlevy/mass_assignment)

---

<div class="post-metadata">

**Author:** ![Brennan\_Dunn1](https://avatars.discourse-cdn.com/v4/letter/b/ea5d25/32.png) [@Brennan\_Dunn1](https://discuss.rubyonrails.org/u/Brennan_Dunn1)\
**Post date:** [May 26, 2009, 12:54am UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775/12 "2009-05-26T00:54:15Z")

</div>

class UsersController \< ApplicationController

def create

@user = User.new

# during signup a user may pick a username

@user.assign(params[:user], [:username, :email, :password, :password\_confirmation])

@user.save!

…

end

I still don’t see why #splice or #except wouldn’t be preferred here?

@user.attributes = params[:user].splice(:username, :email, :password, :password\_confirmation)

-Brennan

---

<div class="post-metadata">

**Author:** ![Hongli\_Lai](https://avatars.discourse-cdn.com/v4/letter/h/51bf81/32.png) [@Hongli\_Lai](https://discuss.rubyonrails.org/u/Hongli_Lai)\
**Post date:** [May 26, 2009, 10:42am UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775/13 "2009-05-26T10:42:27Z")

</div>

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?

---

<div class="post-metadata">

**Author:** ![Lance](https://avatars.discourse-cdn.com/v4/letter/l/7ab992/32.png) [@Lance](https://discuss.rubyonrails.org/u/Lance)\
**Post date:** [May 26, 2009, 5:42pm UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775/14 "2009-05-26T17:42:52Z")

</div>

I'm still struggling a bit for clear reasons why it might be better to filter the attributes controller-side or model-side. The only functional advantage I can think of either way is that if you want a system with overridable defaults, you want to build the filtering into the model. For example, if you had a baseline attr\_accessible declaration and overrode it at calltime with a different set. But eh, combining declarations like that sounds messy and prone to failure.

Or perhaps some security plugin for people who want to start with fewer (or no) assignable fields could create default blacklists for any /is\_.\*/ and /.\*\_id/ fields, to be overridden as needed. Hmm, that actually sounds interesting. Perhaps I'll add that optional module into my plugin. 🙂

Other than that, is the difference entirely aesthetic? I think the "assign(params[:user], ...)" syntax reads better than "attributes = params[:user].slice(...)", but that alone isn't enough reason to change an API.

---

<div class="post-metadata">

**Author:** ![Brennan\_Dunn1](https://avatars.discourse-cdn.com/v4/letter/b/ea5d25/32.png) [@Brennan\_Dunn1](https://discuss.rubyonrails.org/u/Brennan_Dunn1)\
**Post date:** [May 26, 2009, 5:49pm UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775/15 "2009-05-26T17:49:52Z")

</div>

Based on the example given for #assign, it does appear to be just a syntactical difference, and I wouldn’t think anything about AR core should be changed.

When mass assigning attributes, one could always do: @user.attributes = params[:user].slice(\*@user.writeable\_attributes), where User#writeable\_attributes would tie in to your ACL system and return an array of settable attributes for that user type.

-Brennan

---

<div class="post-metadata">

**Author:** ![Mislav](https://avatars.discourse-cdn.com/v4/letter/m/77aa72/32.png) [@Mislav](https://discuss.rubyonrails.org/u/Mislav)\
**Post date:** [May 26, 2009, 6:27pm UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775/16 "2009-05-26T18:27:36Z")

</div>

To me it sounds like a responsibility of the controller. It is the one receiving the parameters, selecting the relevant ones and passing them down to models.

On the other hand, having attribute filtering in the controllers would require a bit of sync between model and controller code. I like to avoid controller having too much knowledge of model attributes, myself.

---

<div class="post-metadata">

**Author:** ![fxn](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/fxn/32/12956_2.png) [@fxn](https://discuss.rubyonrails.org/u/fxn)\
**Post date:** [May 26, 2009, 8:25pm UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775/17 "2009-05-26T20:25:25Z")

</div>

Yeah I've added it

&nbsp;&nbsp;&nbsp;[http://github.com/lifo/docrails/commit/6197606588674bd16e17899e0df15adf2a482ba0](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.

---

<div class="post-metadata">

**Author:** ![Matt\_Jones](https://avatars.discourse-cdn.com/v4/letter/m/258eb7/32.png) [@Matt\_Jones](https://discuss.rubyonrails.org/u/Matt_Jones)\
**Post date:** [May 26, 2009, 8:30pm UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775/18 "2009-05-26T20:30:20Z")

</div>

One other thought - going back to the original example (admin user can mass-assign fields that are normally protected), what about an extra parameter to update\_attributes (and possibly create)? ie:

@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

---

<div class="post-metadata">

**Author:** ![fxn](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/fxn/32/12956_2.png) [@fxn](https://discuss.rubyonrails.org/u/fxn)\
**Post date:** [May 26, 2009, 8:47pm UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775/19 "2009-05-26T20:47:15Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![Lance](https://avatars.discourse-cdn.com/v4/letter/l/7ab992/32.png) [@Lance](https://discuss.rubyonrails.org/u/Lance)\
**Post date:** [May 27, 2009, 12:57am UTC](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775/20 "2009-05-27T00:57:00Z")

</div>

After playing with a reimagined version of attr\_accessible/ attr\_protected in my plugin, I'm much happier with the model-side filtering approach. I think it allows for more interesting and useful defaults.

Since this API is to live only as a plugin for a bit, I'm unsure whether this thread is the place to continue discussion? I think that Xavier's improved documentation is probably all that can/should be done to core at this time. Anyone interested in playing with the mass assignment API is welcome to contact me directly or through GitHub.

[Next page](https://discuss.rubyonrails.org/t/redesigning-mass-attribute-assignment-activerecord-3-0/42775.md?page=2)
