This feature is schedule for: Rails v3.0
ActiveRecord validations, ground zero for anybody learning about Rails, got a lil’ bit of decoupling mojo today with the introduction of validator classes. Until today, the only options you had to define a custom validation was by overriding the validate method or by using validates_each, both of which pollute your models with gobs of validation logic.
ActiveRecord Validators
Validators remedy this by containing granular levels of validation logic that can be reused across your models. For instance, for that classic email validation example we can create a single validator:
1 2 3 4 5 6 |
class EmailValidator < ActiveRecord::Validator def validate() record.errors[:email] << "is not valid" unless record.email =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i end end |
Each validator should implement a validate method, within which it has access to the model instance in question as record. Validation errors can then be added to the base model by adding to the errors collection as in this example.
So how do you tell a validator to operate on a model? validates_with that takes the class of the validator:
1 2 3 |
class User < ActiveRecord::Base validates_with EmailValidator end |
Validation Arguments
This is all well and good, but is a pretty brittle solution in this example as the validator is assuming an email field. We need a way to pass in the name of the field to validate against for a model class that is unknown until runtime. We can do this by passing in options to validates_with which are then made available to the validator at runtime as the options hash. So let’s update our email validator to operate on an email field that can be set by the model requiring validation:
1 2 3 4 5 6 7 |
class EmailValidator < ActiveRecord::Validator def validate() email_field = options[:attr] record.errors[email_field] << "is not valid" unless record.send(email_field) =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i end end |
And to wire it up from the user model:
1 2 3 |
class User < ActiveRecord::Base validates_with EmailValidator, :attr => :email_address end |
Any arguments can be passed into your validators by hitching a ride onto this options hash of validates_with.
Options & Notes
There are also some built-in options that you’ll be very familiar with, namely :on, :if and :unless that define when the validation will occur. They’re all the same as the options to built-in validations like validates_presence_of.
1 2 3 4 |
class User < ActiveRecord::Base validates_with EmailValidator, :if => Proc.new { |u| u.signup_step > 2 }, :attr => :email_address end |
It’s also possible to specify more than one validator with validates_with:
1 2 3 |
class User < ActiveRecord::Base validates_with EmailValidator, ZipCodeValidator, :on => :create end |
While this might seem like a pretty minor update, it allows for far better reusability of custom validation logic than what’s available now. So enjoy.
tags: ruby, rubyonrails

One quick question,
I can also use the validation helper methods (such as validates_uniqueness_of) in the model along with validates_with right?
This looks like a welcome change to me. So where should the validator class files be stored? Under Model, or lib, or will there be a new folder?
<3>
It would be really cool if I could define a to_js method that allowed me to define my javascript validation here as well which would be able to understand and execute it, as long as my JS validator conformed to some sort of regular interface (ie: returns true/false). I’d love to have all my validator code in 1 class.
I just wondering why the options hash is not used by more convenient way, i.e. your example could looks better as:
class EmailValidator < ActiveRecord::Validator def validate(options) email_field = options[:attr] record.errors[email_field] << “is not valid” unless record.send(email_field) =~ /([
\s]+)((?:[-a-z0-9]\.)[a-z]{2,})$/i end endI second Prem’s question. Is this possible:
class EmailValidator < ActiveRecord::Validator validates_presence_if options[:attr].to_sym validates_uniqueness_of options[:attr].to_sym def validate() # rest of logic end end
What if I forget to define validate method in ancestor of ActiveRecord::Validator? Sometime I miss for Java interfaces. But only sometimes ;-)
Once again I. I’ve checked http://github.com/rails/rails/blob/0a558b36eb3858ceeb926ada1388b0bd41da11f7/activerecord/lib/active_record/validator.rb. RuntimeError “You must to override this method” – I like it. :-)
One off-topic thing: The e-mail regular expression is wrong. ^ and $ match the line beginning and end, use \A and \z like here: http://www.rorsecurity.info/journal/2007/4/16/ruby-regular-expression-fun.html
My biggest issue with the way validators currently behave is that they display the model name as the first word in the error message. There are plenty of ways around it but on the applications I’ve worked with it’s been rare that displaying error messages of that format to the user makes sense. Now that validators are getting some love, hopefully they’ll address that as well.
The only thing missing is a funky way of registering validations, like:
ActiveRecord::Base.register_validator :validates_email_format_of, EmailValidator
Can they be mixed with ActiveResource or other non-DB/non-ActiveRecord models?
thanks for the post,
It will be more fun to have
class User < ActiveRecord::Base validate :email_address, EmailValidator end
aka
class User < ActiveRecord::Base validate attribute, validator_class [, other extara options like :on or :if ] end
I should dust off my Singleton Validations plugin… it’s validations at the instance level. I haven’t checked whether it’s working with very recent Railses.
I have written a patch that includes some of the wishes in these comments: https://rails.lighthouseapp.com/projects/8994/tickets/3058-patch-sexy-validations
Could anyone who wants this, please give it a +1. Thanks.