Peter Marklund

Peter Marklund's Home

2009-04-24

Rails Gotcha: ActiveRecord##Base.valid?, errors.empty?, and before_validation Callbacks

The ActiveRecord::Callbacks module (that depends on ActiveSupport::Callbacks) defines a multitude of before and after callbacks for the lifecycle of your ActiveRecord objects. Similar to how before filters in controllers work, if a before_validation callback method returns false, the save process will be aborted. Here is what the API documentation says:

# If the returning value of a +before_validation+ callback can be evaluated to +false+,
# the process will be aborted and Base#save< will return +false+.
# If Base#save! is called it will raise a ActiveRecord::RecordInvalid exception.
# Nothing will be appended to the errors object.

# If a before_* callback returns +false+, all the later callbacks and the associated
# action are cancelled. If an after_* callback returns
# +false+, all the later callbacks are cancelled. Callbacks are generally run in the 
# order they are defined, with the exception of callbacks
# defined as methods on the model, which are called last.

What this means is that if a before_validation callback returns false, then save and valid? will both return false but errors.empty? will return true. Yes, you read correctly, there are no validation errors but the record is not valid. Also, note that the valid? method defined in the ActiveRecord::Base class will never even be invoked.

3 comment(s)

Comments

Nick said 2010-03-24 06:01:

I handle this by using errors.add_to_base( "My reason for returning false" ) before I return false
--------------------------------------------------------------------------------

krukid said 2010-02-24 17:25:

A handled exception in one of before_validation callbacks also seems to be interpreted as a failed validation, ex.: def before_validation unsafe_call rescue rescue_proc end and the ar.save doesn't make it to the validate method. A workaround would be: def before_validation unsafe_call rescue rescue_proc ensure return true end Also, any errors you add manually (self.errors.add) in the callback are dropped if you return true.
--------------------------------------------------------------------------------

Marko said 2009-10-13 18:07:

Just into this issue, thanks for noting it.
--------------------------------------------------------------------------------