update_attribute considered harmful
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
However, going back and reading the effing manual, this is actually the
documented behaviour of
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
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
few notable alternatives to
If you just want to update one attribute and save the entire record, assuming the entire record is valid, use
update(attribute: value)- the method formerly known as
If you want to change a value regardless of the record being valid or not, use
update_colum(column_name, value)which is lower level, doesn't run validations, and only updates what it's been told to.
And do check out David Verhasselts excellent overview of different ways to set attributes in ActiveRecord.