update_attribute considered harmful

Yesterday, we banned usage of ActiveRecord#update_attribute on the Rails team at GoMore.

We stumbled across a gotcha in ActiveRecord#update_attribute which has some surprising - and potentially dangerous - behaviour.

Show me the gotcha!

Consider the following code (assuming User has an email validation):

user = User.find(1)
user.email = "notvalid"
user.valid? #=> false
user.update_attribute(:first_name, "Bob")

I’d expect User 1 to have their name changed to “Bob”, while the email address is unchanged, however…

persisted_user = user.reload
persisted_user.email #=> "invalid"
persisted_user.name #=> "Bob"
persisted_user.valid? #=> false

ActiveRecord has happily saved an invalid User object, because update_attribute bypasses validations. Nevermind the fact that update_attribute is named with a singular attribute; it can still update multiple attributes.

RTFM

However, going back and reading the effing manual, this is actually the documented behaviour of update_attribute:

Updates a single attribute and saves the record.

So all this time I have read the above to mean that it updates a single attribute and saves that change and nothing else to the database, I’ve been wrong. It actually meant that it updates a single attribute and saves the entire record, including all other changed attributes.

But hey, that’s documented as well:

Updates all the attributes that are dirty in this object.

I was surprised, the team at GoMore was surprised, and other people are as well, but yeah, sure, I guess it’s true what the say about assuming.

The tumultuous life of update_attribute

This has been discussed on Rails Core, and the current attitude is, that this isn’t going to get fixed. The method is too widely used to just remove, it seems, and the behaviour has become documented.

update_attribute is no stranger to controversy. It was actually removed from Rails entirely at one point, but it was also brought back again, due to popular demand(?).

What to do instead

Fret not, there are still tons of ways to set attributes in ActiveRecord. A few notable alternatives to update_attribute:

And do check out David Verhasselts excellent overview of different ways to set attributes in ActiveRecord.