opensoul.org

Varying validations

May 10, 2007 code 4 min read

As great as Rails validations are, they’ve left much to be desired for me. I find that I often want to only do partial validations, or use a different set of validations on my model based on who is creating it (admins should be trusted more than visitors) or where it is being created (creating a user on the command line or automated process shouldn’t require acceptance of terms or confirmation of a password).

I’m getting close to the point where I’m so annoyed about this problem that I’m ready to solve it, but I can’t quite figure out the best way to do it. So, I ask you, how do we remedy this?

Here are a couple ideas that I’ve had for changing how validations work.

Fatter model

My first thought is to just add it to the model by declaring “sets” of validations:

class User < ActiveRecord::Base
  validations :terms do
    validates_acceptance_of :terms, :privacy_policy
  end
  validations :confirmation do
    validates_confirmation_of :password
  end
end

And then when you save a model, you can specify what sets to use.

user = User.new(params[:user])
user.valid?(:terms, :confirmation)
user.save(:terms, :conditions)
# or all, possibly the default
user.save(:all)

I could also see this being used for a wizard like interface.

class Foo < ActiveRecord::Base
  validations :step1 do
    validates_presence_of :name, :stuff, :thing
  end
  validations :step2 do
    validates_presence_of :bar, :baz
    validates_acceptance_of :terms
  end
  validations :step3 do
    validates_confirmation_of :password
  end
end

But that shouldn’t really be part of the model

You’re right, these problems really don’t concern the model. The model shouldn’t care were it is being created from, or if it’s being created in one step or five. So what about going to the opposite end of the spectrum, and completely removing validations from the model and declaring them in observer-like classes.

class TermsValidation < ActiveRecord::Validations
  validate :user, :supplier

  validates_acceptance_of :terms, :privacy_policy, :on => :create
end

class Step1Validation < ActiveRecord::Validations
  validate :foo

  validates_presence_of :name, :stuff, :thing
end

And then in the controller:

class UsersController < ActionController::Base
  validate :terms

  def create
    user = User.new(params[:user])
    if user.save
      # all the validations passed
    else
      # a validation failed, handle just like you would normally
    end
  end
end

If we had an action that required different validations than the rest of the controller, we could also use similar approach to what we talked about earlier and declare what validations to use:

class UsersController < ActionController::Base
  validate :terms

  def update
    user = User.find(params[:id])
    user.update_attributes(params[:user], :step2)
  end
end

I like this better, because choosing what set of validations to use does seem more like a controller decision, and not model one, but it is the model’s responsibility to make sure that the data is “valid”, so how do we reconcile this?

How are you working around this problem? Is there an obvious solution that I’m just not seeing?

This content is open source. Suggest Improvements.

@bkeepers

avatar of Brandon Keepers I am Brandon Keepers, and I work at GitHub on making Open Source more approachable, effective, and ubiquitous. I tend to think like an engineer, work like an artist, dream like an astronaut, love like a human, and sleep like a baby.