Peter Marklund

Peter Marklund's Home

Fri March 02, 2007
Programming

Rails Gotcha: ActiveRecord::Base#update_attribute skips validation

It's something others have been bitten by. Although we should know better (why didn't we read the API docs carefully?) we naivley thought we could safely update a single attribute with the update_attribute method. When you invoke my_precious_object.update_attribute(:important_attribute, erroneous_value) then Rails will happily skip validations and save all attributes to the database. This is unlike the sister method update_attributes which updates several attributes and does use validation. I'm sure validation is skipped for a reason but I'm not sure what that reason is. In particular I'm not sure if that reason is strong enough to warrant the inconsistency and the risk of skipping validations.

I for one thought all public methods that create and update an ActiveRecord object were supposed to use validations. That would have seemed natural and even essential. Especially since most Rails apps run on MySQL which doesn't have a great track record when it comes to data integrity.

As far as I can see there is no mention in the Rails book of this validation glitch. I think I should pass a note to Dave Thomas about that.

If you look under the hood of Rails, it turns out update_attribute is equivalent to:

  object.attribute = value
  object.save(false)

The update_attribute method is first defined in the Base class and then overridden by the mixed in Validations module. To see the definitions of the save, update_attribute, and update_attributes methods you would be looking in vain in the Base class, in fact, the code you see there is never used, which feels a little bizarre. Fortunately there are comments in there pointing us to the Validations module.

On a side note, notice that unlike in Java, there is no method overloading on argument signatures in Ruby, so a method is identified solely by its name. When you redefine a method you can effectively change its argument list if you like, although this is probably not a good idea in most cases. The Validations module uses this ability though for the save method to add an optional boolean argument that determines if validation should be performed or not. This in turn is what the redefined update_attribute method makes use of. Questions? :-)

Comments

Peter Marklund said over 7 years ago:

This issue was discussed at the Rails core mailing list.

--------------------------------------------------------------------------------

railsfan said over 6 years ago:

Hello Peter,

I think the reason it doesn't do validations is sometimes you don't want to validate. Example: Suppose I have a User who is logged in and just wants to change his address fields but not email or password fields required by validations. Then if I build a form without email or password fields I cannot save using update_attributes which insists on validations. Is that right?

Thanks,
railsfan

--------------------------------------------------------------------------------

amy said over 6 years ago:

the big reason I can think of for skipping validations is the necessity of making an app back-compatible with old data. Example: company implements new tracking system that requires a project code for new entries. Old entries are grandfathered in. A user tries to update some other attribute of an old entry, and is informed that they must supply a project code. "Wha?" says the user. "I wasn't even trying to modify that part!" In my last big project, I remember having to code for this problem all the time, as we had a lot of old, messy data and validations that were always changing.

I suppose a better approach than skipping validations entirely would be to have some way of applying validations conditionally. new to rails, but perhaps such a way already exists?

--------------------------------------------------------------------------------

Peter Marklund said over 6 years ago:

Amy,
I think in your use case where you want to save a subset of attributes you still presumably want to do validation for the attributes that you are saving. Remember that all attributes that are selected into the ActiveRecord object that you are saving/updating get updated in the database. Thus, to save a single attribute you would do:

# Skipping validation for subject
Post.find(1, :select => "id, subject").update_attribute(:subject, "foobar")

# With validation for subject
Post.find(1, :select => "id, subject").update_attributes(:subject => "foobar')

--------------------------------------------------------------------------------

amy said over 6 years ago:

Wait, now I'm really confused! My agile dev with rails book says that when you use a :select option with a find, the resulting ActiveRecord object is automatically marked :readonly and cannot be saved back. So does your example work?

In any event, I see now the problem I am thinking of wouldn't be solved by update_attribute anyway. What I want is to be able to conditionally skip the validation of one particular field if I'm grandfathering in data. So imagine I have an editable form with some data fields, one of which is the "new required ID". Records originally created after a certain date must have a valid new required ID in them. Records created before that date can have whatever data they originally had (nothing, or an invalid value) but if it's changed, then it must be validated. I want the user to be able to edit all the values in the record, but only validate that particular field if it is from a record created after a certain date, OR if its value has actually been changed by the user. Anyway, I looked it up, and apparently I _can_ do conditional validations by passing my validation method an :if option. too bad the case I needed this for was in software I wrote 5 years ago in Java!

cheers!

--------------------------------------------------------------------------------

Peter Marklund said over 6 years ago:

Indeed, it does say in the Rails book that the :select and :joins options automatically turn on the :readonly option. However, in edge Rails that seems to have been taken out. I think in Rails 1.2.3 the :joins option still turns on readonly, but the :select option doesn't.

--------------------------------------------------------------------------------

Andrew Hobson said over 6 years ago:

Also note that this makes in_place_edit_for useless, IMO, because it bypasses validation.

--------------------------------------------------------------------------------

david@veeras.com said over 6 years ago:

we have to add the selected item from the list box in a form to the database.
we had created a seperate column in the database for that purpose.
we understood that we can use Update attributes. how to use it.
Please help us in the process

--------------------------------------------------------------------------------

GregL said over 6 years ago:

It is for updating simple vars, like booleans. No need to validate those.

--------------------------------------------------------------------------------

Raghu said over 6 years ago:

Can you give me idea of how to skip validations for the update_attributes methods?...as therez no such method in validation.rb under the Active record module that allows skipping of validations for the update_attibutes method

--------------------------------------------------------------------------------