Computers are like Old Testament gods; lots of rules and no mercy. | ||
--Joseph Campbell |
The Validations API in ActiveRecord
allows you to declaratively define valid states for your model objects. The validation methods hook into the life cycle of an ActiveRecord
model object and are able to inspect the object to determine whether certain attributes are set, have values in a given range, or pass any other logical hurdles that you specify.
In this chapter, we’ll describe the validation methods available and how to use them effectively. We’ll explore how those validation methods interact with your model’s attributes and how the built-in error-messaging system messages can be used effectively in your application’s user interface to provide descriptive feedback.
Finally, we’ll also cover an important RubyGem named Validatable
, which goes beyond Rails’ native capabilities by allowing you to define different sets of validation criteria for a given model object, depending on the role it is currently playing in your system.
Validation problems are also known as (drumroll please...): errors! Every ActiveRecord
model object contains a collection of errors, accessible (unsurprisingly) as the errors
attribute. It’s an instance of the class ActiveRecord::Errors
and it’s defined in the file lib/active_record/validations.rb
along with the rest of the validation code.
When a model object is valid, the errors
collection is empty. In fact, when you call valid?
on a model object, a series of steps to find errors is taken as follows (slightly simplified):
Clear the errors
collection.
Run validations.
Return whether the model’s errors
collection is now empty or not.
If the errors
collection ends up empty, the object is valid. Simple as that.
In some of the validation methods described in this chapter, the ones where you have to write the actual validation logic yourself, you mark an object invalid by adding items to the errors
collection using its add
methods.
We’ll cover the methods of the Errors
class in some more detail later on. It makes more sense to look at the validation methods themselves first.
Whenever possible, you should set validations for your models declaratively by using one or more of the following class methods available to all ActiveRecord
classes. Unless otherwise noted, all of the validates
methods accept a variable number of attributes, plus options. There are some options for these validation methods that are common to all of them, and we’ll cover them at the end of the section.
Many web applications have screens in which the user is prompted to agree to terms of service or some similar concept, usually involving a check box. No actual database column matching the attribute declared in the validation is required; when you call this method, it will create virtual attributes automatically for each named attribute you specify. I see this validation as a type of syntax sugar since it is so specific to web application programming.
class Account < ActiveRecord::Base validates_acceptance_of :privacy_policy, :terms_of_service end
When the validates_acceptance_of
validation fails, an error message is stored in the model object reading “attribute must be accepted.”
The :accept
option makes it easy to change the value considered acceptance. The default value is "1"
, which matches the value supplied by check boxes generated using Rails helper methods.
class Cancellation < ActiveRecord::Base validates_acceptance_of :account_cancellation, :accept => 'YES' end
If you use the preceding example in conjunction with a text field connected to the account_cancellation
attribute, the user would have to type the word YES in order for the cancellation object to be valid.
When a given model has associated model objects that also need to be valid when it is saved, you use the validates_associated
method, which works with any kind of association. When the validation is invoked (on save, by default), the valid?
method of each associated object will be called.
class Invoice < ActiveRecord::Base has_many :line_items validates_associated :line_items end
It’s worth noting that careless use of validates_associated
can result in a circular dependency and cause infinite recursion. Well, not infinite, but it will blow up. Given the preceding example, do not do the following on the LineItem
class:
class LineItem < ActiveRecord::Base belongs_to :invoice validates_associated :invoice end
This validation will not fail if the association is nil because it hasn’t been set yet. If you want to make sure that the association is populated and valid, you have to use validates_associated
in conjunction with validates_presence_of
(covered later in this chapter).
The validates_confirmation_of
method is another case of syntactic sugar for web applications, since it is so common to include dual-entry text fields to make sure that the user entered critical data such as passwords and e-mail address correctly. This validation will create a virtual attribute for the confirmation value and compare the two attributes to make sure they match in order for the model to be valid.
Here’s an example, using our fictional Account
model again:
class Account < ActiveRecord::Base validates_confirmation_of :email, :password end
The user interface used to set values for the Account
model would need to include extra text fields named with a _confirmation
suffix, and when submitted, the value of those fields would have to match in order for this validation to pass.
The validates_each
method is a little more free-form than its companions in the validation family in that it doesn’t have a predefined validation function. Instead, you give it an array of attribute names to check, and supply a Ruby block to be used in checking each attribute’s validity.
The block function designates the model object as valid or not by merit of adding to its errors
array or not. The return value of the block is ignored.
There aren’t too many situations where this method is necessary, but one plausible example is when interacting with external services for validation. You might wrap the external validation in a façade specific to your application, and then call it using a validates_each
block:
class Invoice < ActiveRecord::Base validates_each :supplier_id, :purchase_order do |record, attr, value| record.errors.add(attr) unless PurchasingSystem.validate(attr, value) end end
Notice that parameters for the model instance (record
), the name of the attribute, and the value to check are passed as block parameters.
The validates_inclusion_of
method and its complement, validates_exclusion_of
, are pretty cool, but unless you’re super-thorough with your application requirements, I’ll bet a small sum that you haven’t realized yet why you need them.
These methods take a variable number of attribute names and an :in
option. When they run, they check to make sure that the value of the attribute is included (or excluded, respectively) in the enumerable object passed as the :in
option.
The examples in the Rails docs are probably some of the best illustrations of their use, so I’ll take inspiration from them:
class Person < ActiveRecord::Base validates_inclusion_of :gender, :in => ['m','f'], :message => 'O RLY?' class Account validates_exclusion_of :login, :in => ['admin', 'root', 'superuser'], :message => 'Borat says "Naughty, naughty!"' end
Notice that in the examples I’ve introduced usage of the :message
option, common to all validation methods, to customize the error message constructed and added to the Errors
collection when the validation fails. We’ll cover the default error messages and how to effectively customize them a little further along in the chapter.
This validation is provided by a plugin, but I’ve found it so useful in day-to-day work that I had to include it. It checks that a foreign key in a belongs_to
association references an exisiting record in the database. You can think of it as a foreign-key constraint, except in your Rails code. It also works fine with polymorphic belongs_to
associations.
class Person < ActiveRecord::Base belongs_to :address validates_existence_of :address end
Josh Susser came up with the idea and the plugin and described it on his blog:
The thing that’s always frustrated me is that there isn’t a validation to enforce that a foreign key references a record that exists. Sure,
validates_presence_of
will make sure you have a foreign key that isn’t nil. Andvalidates_associated
will tell you if the record referenced by that key passes its own validations. But that is either too little or too much, and what I want is in the middle ground. So I decided it was time to roll my own. http://blog.hasmanythrough.com/2007/7/14/validate-your-existence
To install the plugin just type the following in your project directory:
$ script/plugin install http://svn.hasmanythrough.com/public/plugins/validates_existence/
As for options, if :allow_nil => true
, then the key itself may be nil
and no validation will occur. A non-nil key will cause a query to be issued to make sure that the foreign object exists in the database. The default error message is “does not exist”, but can be overriden just like other validations using the :message
option.
To use validates_format_of
, you’ll have to know how to use Ruby regular expressions. Pass the method one or more attributes to check, and a regular expression as the (required) :with
option. A good example, as shown in the Rails docs, is checking for a valid e-mail address format:
class Person < ActiveRecord::Base validates_format_of :email, :with => /A([^@s]+)@((?:[-a-z0-9]+.)+[a-z]{2,})/i end
By the way, that example is totally not an RFC-compliant email address format checker[1].
The validates_length_of
method takes a variety of different options to let you concisely specify length constraints for a given attribute of your model.
class Account < ActiveRecord::Base validates_length_of :login, :minimum => 5 end
The :minimum
and :maximum
options work as expected, but don’t use them together. To specify a range, use the :within
option and pass it a Ruby range, as in the following example:
class Account < ActiveRecord::Base validates_length_of :login, :within => 5..20 end
To specify an exact length of an attribute, use the :is
option:
class Account < ActiveRecord::Base validates_length_of :account_number, :is => 16 end
Rails gives you the ability to generate detailed error messages for validates_length_of
via the :too_long
, :too_short
, and :wrong_length
options. Use %d
in your custom error message as a placeholder for the number corresponding to the constraint.
class Account < ActiveRecord::Base validates_length_of :account_number, :is => 16, :wrong_length => "should be %d characters long" end
The somewhat clumsily named validates_numericality_of
method is used to ensure that an attribute can only hold a numeric value. The :integer_only
option lets you further specify that the value should only be an integral value and defaults to false.
class Account < ActiveRecord::Base validates_numericality_of :account_number, :integer_only => true end
Last but not least is one of the more common validation methods, :validates_presence_of
, which is used to denote mandatory attributes. This method checks whether the attribute is blank via Rails’ blank?
method, defined on Object
, which returns true
for values that are nil
or a blank string ""
.
class Account < ActiveRecord::Base validates_presence_of :login, :email, :account_number end
When you’re trying to ensure that an association is present, write your association against its foreign key attribute, not the association variable itself. Note that the validation will fail in cases when both the parent and child object are unsaved (since the foreign key will be blank).
The validates_uniqueness_of
method ensures that the value of an attribute is unique for all models of the same type. This validation does not work by adding a uniqueness constraint at the database level. It does work by constructing and executing a query looking for a matching record in the database. If any record is returned when this method does its query, the validation fails.
class Account < ActiveRecord::Base validates_uniqueness_of :login end
By specifying a :scope
option, additional attributes can be used to determine uniqueness. You may pass :scope
one or more attribute names as symbols (putting multiple symbols in an array).
class Address < ActiveRecord::Base validates_uniqueness_of :line_two, :scope => [:line_one, :city, :zip] end
It’s also possible to specify whether to make the uniqueness constraint case-sensitive or not, via the :case_sensitive
option (ignored for nontextual attributes).
In the course of using join models (with has_many :through
), it seems pretty common to need to make the relationship unique. Consider an application that models students, courses, and registrations with the following code:
class Student < ActiveRecord::Base has_many :registrations has_many :courses, :through => :registrations end class Registration < ActiveRecord::Base belongs_to :student belongs_to :course end class Course < ActiveRecord::Base has_many :registrations has_many :students, :through => :registrations end
How do you make sure that a student is not registered more than once for a particular course? The most concise way is to use validates_uniqueness_of
with a :scope
constraint. The important thing to remember with this technique is to reference the foreign keys, not the names of the associations themselves:
class Registration < ActiveRecord::Base belongs_to :student belongs_to :course validates_uniqueness_of :student_id, :scope => :course_id, :message => "can only register once per course" end
Notice that since the default error message generated when this validation fails would not make sense, I’ve provided a custom error message that will result in the expression: “Student can only register once per course.”
Whenever you do so-called bang operations (such as save!
) or operations that save behind the scenes, and a validation fails, you should be prepared to rescue ActiveRecord::RecordInvalid
. Validation failures will cause RecordInvalid
to be raised and its message will contain a description of the failures.
Here’s a quick example from one of my applications that has pretty restrictive validations on its User
model:
>> u = User.new => #<User ...> >> u.save! ActiveRecord::RecordInvalid: Validation failed: Name can't be blank, Password confirmation can't be blank, Password is too short (minimum is 5 characters), Email can't be blank, Email address format is bad
The following options apply to all of the validation methods.
In many cases, you only want to trigger a validation if a value is present and the absence of a value is not a problem. The :allow_nil
option skips the validation if the value of the attribute is nil
. Remember that this option only checks for nil
, and empty strings ""
are not considered nil.
The :if
option is covered in the next section, “Conditional Validation.”
As we’ve discussed earlier in the chapter, the way that the validation process registers failures is by adding items to the Errors
collection of the model object being checked. Part of the error item is a specific message describing the validation failure. All of the validation methods accept a :message
option so that you can override the default error message format.
class Account < ActiveRecord::Base validates_uniqueness_of :login, :message => "is already taken" end
By default, validations are run on save (both create and update operations). If you need to do so, for whatever reason, you can limit a given validation to just one of those operations by passing the :on
option either :create
or :update
.
One good use for :on => :create
is in conjunction with validates_uniqueness_of
, since checking uniqueness with a query on large datasets can be time-consuming.
class Account < ActiveRecord::Base validates_uniqueness_of :login, :on => :create end
But wait a minute—wouldn’t that pose a problem if the account model was updated later on with a nonunique value? That’s where attr_protected
comes in. Sensitive attributes of your model should be protected from mass assignment using the attr_protected
method. In your controller action that creates new accounts, you’ll have to grab the login
parameter and set it manually.
All validation methods also accept an :if
option, to determine at runtime (and not during the class definition) whether the validation needs to be run or not.
The following evaluate_condition
method, from ActiveRecord::Validations
, is called with the value of the :if
option as the condition
parameter and the model object to be validated as record
:
# Determine from the given condition whether or not to validate the # record, (whether a block, procedure, method or string.) def evaluate_condition(condition, record) case condition when Symbol: record.send(condition) when String: eval(condition, binding) else if condition_block?(condition) condition.call(record) else raise ActiveRecordError, "Needs to be a symbol, string (to be eval'ed), or a proc" end end end
As can be discerned by the case
statement in the implementation of the method, the following three types of arguments can be supplied as an :if
option:
Symbol
. The name of a method to invoke as a symbol. This is probably the most common option, and offers the best performance.
String
. A snippet of Ruby code to eval
might be useful when the condition is really short, but keep in mind that eval’ing statements is relatively slow.
Block
. A proc to be call
’d. Perhaps the most elegant choice for one-line conditionals.
When does it make sense to use conditional validations? The answer is: whenever an object can be validly persisted in more than one state.
A very common example, since it is used by the acts_as_authenticated
plugin, involves the User
(or Person
) model, used for login and authentication.
validates_presence_of :password, :if => :password_required? validates_presence_of :password_confirmation, :if => :password_required? validates_length_of :password, :within => 4..40, :if=>:password_required? validates_confirmation_of :password, :if => :password_required?
This code is not DRY (meaning that it is repetitive). You can learn how to refactor it using the with_options
method described in Chapter 14, “Login and Authentication.” Usage and implementation of the acts_as_authenticated
plugin is covered in greater detail in Chapter 14.
There are only two cases when a (plaintext) password field should be required in order for the model to be valid.
protected def password_required? crypted_password.blank? || !password.blank? end
The first case is if the crypted_password
attribute is blank, because that means we are dealing with a new User
instance that has not been given a password yet. The other case is when the password
attribute itself is not blank; perhaps this is happening during an update operation and the user is attempting to reset her password.
Here’s a quick reference of the default wording for error messages, pulled straight out of the Rails codebase:
@@default_error_messages = { :inclusion => "is not included in the list", :exclusion => "is reserved", :invalid => "is invalid", :confirmation => "doesn't match confirmation", :accepted => "must be accepted", :empty => "can't be empty", :blank => "can't be blank", :too_long => "is too long (maximum is %d characters)", :too_short => "is too short (minimum is %d characters)", :wrong_length => "is the wrong length (should be %d characters)", :taken => "has already been taken", :not_a_number => "is not a number" }
As we stated previously, the name of the attribute is capitalized and prepended to the beginning of those default error messages to create the validation failure message. Remember, you can override the default message by using the :message
option.
Some methods are provided to allow you to add validation errors to the collection manually and alter the state of the Errors
collection.
Adds an error message related to the object state itself and not the value of any particular attribute. Make your error messages complete sentences, because Rails does not do any additional processing of them to make them readable.
Adds an error message related to a particular attribute. The message should be a sentence fragment that reads naturally when prepended with the capitalized name of the attribute.
It’s also possible to check the Errors
object for validation failures on specific attributes with a couple of methods.
Returns true or false depending on whether there are validation errors associated with attribute
.
Has multiple return types depending on the state of the errors collection for an attribute
. Returns nil
if no errors are associated with the specified attribute. Returns an error message string if only one error is associated with the specified attribute. Finally, returns an array of error message strings if more than one error is associated with the specified attribute.
We’ve now reached the matter of custom validation methods, which you might choose to employ if the normal declarative validation methods are not cutting it for you.
Earlier in the chapter, I described the process used to find validation errors, with a disclaimer that my explanation was slightly simplified. Here is the real implementation, since it is quite elegant and readable in my opinion, and helps illuminate where custom validation logic can be added.
def valid? errors.clear run_validations(:validate) validate if new_record? run_validations(:validate_on_create) validate_on_create else run_validations(:validate_on_update) validate_on_update end errors.empty? end
There are three calls to run_validations
, which is where your declarative validations have been lined up, ready to check your object, if you’ve defined any. Then there are those three callback (abstract?) methods, that is, methods that are purposely left without an implementation in the Validations
module. They are intended to be overwritten in your own ActiveRecord
model if you need them.
Custom validation methods are useful for checking the state of your object holistically, not just based on individual attributes. For lack of a better example, let’s assume that you are dealing with a model object with a set of three integer attributes (:attr1
, :attr2
, and :attr3
) and a precalculated total attribute (:total
). The total must always equal the sum of the three attributes:
class CompletelyLameTotalExample < ActiveRecord::Base def validate if total != (attr1 + attr2 + attr3) errors.add_to_base("The total doesn't add up!") end end end
Remember: The way to mark an object as invalid is to add to its Errors
collection. The return value of a custom validation method is not used.
The Validations
module mixed into ActiveRecord::Base
affects three instance methods, as you can see in the following code snippet (from activerecord/lib/active_record/validations.rb
in the Rails codebase):
def self.included(base) # :nodoc: base.extend ClassMethods base.class_eval do alias_method_chain :save, :validation alias_method_chain :save!, :validation alias_method_chain :update_attribute, :validation_skipping end end
The methods save
, save!
, and update_attribute
are affected. The validation process on save
and save!
can be skipped by passing in false
as a method parameter.
The first time I came across save(false)
in Rails code, it drove me a little batty. I thought to myself, “I don’t remember save
having a parameter,” and when I checked the API docs, that was indeed the case! Figuring the docs must be lying, I dove into the codebase and checked the implementation of the save
method in ActiveRecord::Base
. No parameter. “WTF, welcome to the wonderful world of Ruby,” I thought to myself. “How the heck am I not getting a 1 for 0 argument error here?”
Eventually I figured it out, or maybe some kind #cabooser
clued me in: the regular Base#save
method is replaced when the validations module is mixed in, which it is by default. As a result of using alias_method_chain
, you end up with a public, yet undocumented, save_without_validation
method, which is probably a lot more maintainable than save(false)
.
What about update_attribute
? The validations module overwrites the default implementation and makes it call save(false)
. It’s short, so I’ll include it here:
def update_attribute_with_validation_skipping(name, value) send(name.to_s + '=', value) save(false) end
That’s why update_attribute
doesn’t invoke validations, yet its companion method update_attributes
does, a question that comes up quite often on the mailing list. Whoever wrote the API docs believes that this behavior is “especially useful for Boolean flags on existing records.”
I don’t know if that is entirely true or not, but I do know that it is the source of ongoing contention in the community. Unfortunately, I don’t have much more to add other than some simple common-sense advice: Be very careful using the update_attribute
method. It can easily persist your model objects in invalid states.
In this (relatively speaking) short chapter, we covered the ActiveRecord Validations API in-depth. One of the most appealing aspects of Rails is how we can declaratively specify the criteria for determining the validity of model objects.
1. | If you need to validate addresses try the plugin at http://code.dunae.ca/validates_email_format_of. |
18.116.60.62