validates :rails_3, :awesome => true
Sun Jan 31 12:17:00 -0800 2010
The new validation methods in Rails 3.0 have been extracted out to Active Model, but in the process have been sprinkled with DRY goodness…
As you would know from Yehuda’s post on Active Model abstraction, in Rails 3.0, Active Record now mixes in many aspects of Active Model, including the validates modules.
Before we get started though, your old friends still exist:
- validates_acceptance_of
- validates_associated
- validates_confirmation_of
- validates_each
- validates_exclusion_of
- validates_format_of
- validates_inclusion_of
- validates_length_of
- validates_numericality_of
- validates_presence_of
- validates_size_of
- validates_uniqueness_of
Are still around and not going anywhere, but Rails version 3 offers you some cool, nay, awesome alternatives:
Introducing the validates method
The Validates method accepts an attribute, followed by a hash of validation options.
Which means you can type something like:
class Person < ActiveRecord::Base validates :email, :presence => true end
The options you can pass in to validates are:
- :acceptance => Boolean
- :confirmation => Boolean
- :exclusion => { :in => Ennumerable }
- :inclusion => { :in => Ennumerable }
- :format => { :with => Regexp }
- :length => { :minimum => Fixnum, maximum => Fixnum, }
- :numericality => Boolean
- :presence => Boolean
- :uniqueness => Boolean
Which gives you a huge range of easily usable, succinct options for your attributes and allows you to place your validations for each attribute in one place.
So for example, if you had to validate name and email, you might do something like this:
# app/models/person.rb
class User < ActiveRecord::Base
validates :name, :presence => true,
:length => {:minimum => 1, :maximum => 254}
validates :email, :presence => true,
:length => {:minimum => 3, :maximum => 254},
:uniqueness => true,
:format => {:with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i}
end
This allows us to be able to look at a model and easily see the validations in one spot for each attribute, win for code readability!
Extracting Common Use Cases
However, the :format => {:with => EmailRegexp} is a bit of a drag to retype everywhere, and definitely fits the idea of a reusable validation that we might want to use in other models.
And what if you wanted to use a really impressive Regular Expression that takes more than a few characters to type to show that you know how to Google?
Well, validations can also except a custom validation.
To use this, we first make an email_validator.rb file in Rails.root’s lib directory:
# lib/email_validator.rb
class EmailValidator < ActiveModel::EachValidator
EmailAddress = begin
qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]'
dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]'
atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-' +
'\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+'
quoted_pair = '\\x5c[\\x00-\\x7f]'
domain_literal = "\\x5b(?:#{dtext}|#{quoted_pair})*\\x5d"
quoted_string = "\\x22(?:#{qtext}|#{quoted_pair})*\\x22"
domain_ref = atom
sub_domain = "(?:#{domain_ref}|#{domain_literal})"
word = "(?:#{atom}|#{quoted_string})"
domain = "#{sub_domain}(?:\\x2e#{sub_domain})*"
local_part = "#{word}(?:\\x2e#{word})*"
addr_spec = "#{local_part}\\x40#{domain}"
pattern = /\A#{addr_spec}\z/
end
def validate_each(record, attribute, value)
unless value =~ EmailAddress
record.errors[attribute] << (options[:message] || "is not valid")
end
end
end
As each file in the lib directory gets loaded automatically by Rails, and as our class inherits from ActiveModel::EachValidator the class name is used to create a dynamic validator that you can then use in any object that makes use of the ActiveModel::Validations mix in, such as Active Record objects.
The name of the dynamic validation option is based on whatever is to the left of “Validator” down-cased and underscorized.
So now in our User class we can simply change it to:
# app/models/person.rb
class User < ActiveRecord::Base
validates :name, :presence => true,
:length => {:minimum => 1, :maximum => 254}
validates :email, :presence => true,
:length => {:minimum => 3, :maximum => 254},
:uniqueness => true,
:email => true
end
Notice the :email => true call? This is much cleaner and simple, and more importantly, reusable.
Now in our console, we will see something like:
$ ./script/console
Loading development environment (Rails 3.0.pre)
?> u = User.new(:name => 'Mikel', :email => 'bob')
=> #<User id: nil, name: "Mikel", email: "bob", created_at: nil, updated_at: nil>
>> u.valid?
=> false
>> u.errors
=> #<OrderedHash {:email=>["is not valid"]}>
With our custom error message “is not valid” showing up in the email.
Class Wide Validations
But what if you had, say, three different models, users, visitors and customers, all of which shared some common validations, but were different enough that you had to separate them out?
Well, you could use another custom validator, but pass it to your models as a validates_with call:
# app/models/person.rb class User < ActiveRecord::Base validates_with HumanValidator end # app/models/person.rb class Visitor < ActiveRecord::Base validates_with HumanValidator end # app/models/person.rb class Customer < ActiveRecord::Base validates_with HumanValidator end
You could then make a file in your lib directory like so:
class HumanValidator < ActiveModel::Validator
def validate(record)
record.errors[:base] << "This person is dead" unless check(human)
end
private
def check(record)
(record.age < 200) && (record.age > 0)
end
end
Which is an obviously contrived example, but would produce this result in our console:
$ ./script/console
Loading development environment (Rails 3.0.pre)
>> u = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
>> u.valid?
=> false
>> u.errors
=> #<OrderedHash {:base=>["This person is dead"]}>
Trigger times
As you would expect, any validates method can have the following sub options added to them:
- :on
- :if
- :unless
- :allow_blank
- :allow_nil
Each of these can take a call to a method on the record itself. So we could have:
class Person < ActiveRecord::Base
validates :post_code, :presence => true, :unless => :no_postcodes?
def no_postcodes?
['TW'].include?(country_iso)
end
end
I think you can see this gives you a huge amount of flexibility.
Credits
Kudos to Jamie Hill, José Valim and Joshua Peek for getting the patch in.

Sun Feb 07 05:58:55 -0800 2010
“true if [‘TW’].include?(country_iso)” is a tautology.
Mon Feb 01 03:28:04 -0800 2010
really nice post.Thanks for sharing it.
Rails 3 is gonna rock…
Sun Jan 31 15:41:23 -0800 2010
These posts are extremely helpful. It’s hard to know in advance just how Rails 3 apps will differ from the status quo; since many of the changes are subtle, it’s hugely helpful to get an early peek at them. Thanks, and keep up the great work!
Mon Feb 01 18:19:40 -0800 2010
Thanks for sharing. It’s something I’d like to see in Rails for some time.
Tue Feb 02 11:00:10 -0800 2010
Very good article. Thanks.
Sat Feb 13 06:06:40 -0800 2010
Couldn’t find the case sensitive option mentioned anywhere for the new method. Here’s how it works -
validates :email,
{:uniqueness => {:case_sensitive => false}}
Mon Feb 15 17:26:50 -0800 2010
@Sigi – thanks, added, @ziljian, cool!
Fri Feb 19 02:21:50 -0800 2010
On using Validation in a non ActiveRecord, at what point of execution is Validation triggered?
Fri Feb 19 07:33:00 -0800 2010
@Josef, it isn’t directly, you have to call the valid? method to determine how you want to handle the object being valid or invalid, eg, save or not to the database.
Mon Mar 15 02:48:47 -0700 2010
Thanks for the writeup, much more in-depth than my brief examples. I’d like to see a lot of this info make it into Rails as a documentation patch as it really explains well the usecase for the validates method.