Action Dependent Validations and Why :on => :update is Bad

ActiveRecord validations typically apply all the time. For example, a User object may be required to always have an email_address.

class User < ActiveRecord::Base
  validates_presence_of :email_address
end

But validations can also be set depending on the action being taken on the object. For example, let's say when a User is first created it does not need to have a password. Before the user can set his or her password, he needs to click an activation link in an e-mail. We can tell ActiveRecord to only require a password when updating the User.

class User < ActiveRecord::Base
  validates_presence_of :password, :on => :update
end

But :on => :update is almost never what you want.

The Smell

Let's take a quick look at the validation behavior of this workflow. We initially create a User with only an e-mail address.

  User.create! :email_address => "daniel.manges@gmail.com"
  #=> #<User id: 1, email_address: "daniel.manges@gmail.com", password: nil>

So far, so good. Now what if something comes up where we want to verify that all the records in the database are valid? We'll find the user and make sure valid? returns true.

user = User.find(1)
user.valid?
#=> false

user.errors.full_messages
#=> ["Password can't be blank"]

The record is invalid. But is it really? No. Users don't have to have passwords until activating. I think for any record in the database, you should be able to load the object, call valid?, and get true. But this isn't just a theoretical issue, running validations :on => :update usually causes actual problems.

The Intention of Validating on Update

We spend a lot of time as software developers trying to determine the intention of other developers. If code is fairly clean, we can easily tell what it does. But determining if the some of the behavior is intended or incidental is more difficult. Consider this code:

class User < ActiveRecord::Base
  validates_presence_of :password, :on => :update
end

I translate this code into: the very first time a User is updated after being created it must have a password.

That's a really strong constraint. What if I want to update the user another way without setting a password? If the user is activating his account, I want him to set his password. But an admin should be allowed to change some attributes on the user without setting the password. Because of the constraint imposed by this validation, what happens a lot of time is this:

user.some_flag = true
user.save(false)

If you're not familiar with save(false), it saves the record without running validations. Because the validations won't allow the user to be updated without setting a password, it's easy to resort to doing this. But it's dangerous - skipping validations can end up being a nightmare. You can't add a new validation and be sure that it will never be violated - you have to look for all occurrences of save(false) to make sure none of them are circumventing.

The Solution

Let's go back to the requirement. When a user activates his account, he must set a password. To implement this, we need a way to have a validation run only on activation. Here's one way to do this:

class User < ActiveRecord::Base
  
  def save_on_activation
    return false unless valid_for_activation?
    save!
  end
  
  def valid_for_activation?
    valid? # run all validations
    errors.add(:password, "cannot be blank") if password.blank?
    errors.empty?
  end
end

Having save_on_activation and valid_for_activation? methods makes it easy to control when the validations apply. If you like the declarative nature of the ActiveRecord validations, you could set some state and then use the :if option like this:

class User < ActiveRecord::Base
  validates_presence_of :password, :if => :activating?
  
  def save_on_activation
    @activating = true
    save
  ensure
    @activating = false
  end
  
  def activating?
    @activating
  end
end

With this implementation, setting the @activating instance variable causes the validation to be run.

Validation Groups

If you need a lot of separate validation groups like this, managing all the validations can become tricky. For example, this save_on_activation method applies the validation on the appropriate action, but what about when a user is updating his profile, which includes a password edit field? We need to make sure he can't set a blank password. So we want this validation to run on both activation and edit.

Validatable is a validation framework that allows grouping validations in a declarative style. I haven't used it with Rails 2.x, but with Rails 1.2 you could include Validatable in an ActiveRecord class and it would just work. Anyway, with validatable the code would look like this:

class User < ActiveRecord::Base
  include Validatable
  
  validates_presence_of :password, :groups => [:activation, :edit]
  
  def save_on_activation
    return false unless valid_for_activation?
    save  
  end
end

Summary

For some requirements :on => :update seems to be the answer. One is the example from this blog post, where an attribute isn't required when something is created, but it is required when the record is updated later. Another is legacy data. It's typical for legacy data not to comply with a validation, but new records and updated legacy records should adhere to it. In either of these scenarios I think it's better to make custom save and valid methods than it is to use :on => :update.

Dan Manges Hi, I'm Dan Manges. I'm currently building Root. Previously I was the founding CTO of Braintree. Send me an email.
blog comments powered by Disqus