Home > Articles

  • Print
  • + Share This
This chapter is from the book

This chapter is from the book

8.2 The Simple Declarative Validations

Whenever possible, you should set validations for your models declaratively by using one or more of the following class methods available to all Active Record classes (listed alphabetically).

8.2.1 validates_absence_of

The validates_absence_of method ensures specified attributes are blank. It uses the blank? method, defined on Object, which returns true for values that are nil or a blank string "". It is the polar opposite of the commonly used validates_presence_of validation method, covered later in this section.

class Account < ActiveRecord::Base
  validates_absence_of :spambot_honeypot_field

When the validates_absence_of validation fails, an error message is stored in the model object reading “attribute must be blank.”

8.2.2 validates_acceptance_of

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

Note that you can use this validation with or without a boolean column on the table backing your model. A transient attribute will be created if necessary.

Choose to store the value in the database only if you need to keep track of whether the user accepted the term, for auditing or other reasons.

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. Sometimes a little more attention is required from the user than just checking a box.

class Cancellation < ActiveRecord::Base
  validates_acceptance_of :account_cancellation, accept: 'YES'

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.

8.2.3 validates_associated

Used to ensure that all associated objects are valid on save. Works with any kind of association and is specific to Active Record (not Active Model.) We emphasize all because the default behavior of has_many associations is to ensure the validity of only their new child records on save.

A validates_associated on belongs_to will not fail if the association is nil. 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.

8.2.4 validates_confirmation_of

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 email 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 :password

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. A simplified example of matching view code is provided.

= form_for account do |f|
  = f.label :login
  = f.text_field :login
  = f.label :password
  = f.password_field :password
  = f.label :password_confirmation
  = f.password_field :password_confirmation
  = f.submit

8.2.5 validates_each

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.

Sorry, I realize that was a mouthful. Perhaps an example would help.

class Invoice < ActiveRecord::Base
  validates_each :supplier_id, :purchase_order do |record, attr, value|
    record.errors.add(attr) unless PurchasingSystem.validate(attr, value)

Notice that parameters for the model instance (record), the name of the attribute as a symbol, and the value to check are passed as parameters to the block. As per usual practice, the model object is marked valid or not by merit whether anything has been added to its errors object. 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 facade specific to your application and then call it using a validates_each block.

8.2.6 validates_format_of

To use validates_format_of, you’ll have to know how to use Ruby regular expressions.1 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 email address format:

class Person < ActiveRecord::Base
  validates_format_of :email,
    with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/

By the way, that example is totally not an RFC-compliant email address format checker. If you need to validate email addresses try the plugin at https://github.com/spectator/validates_email.

8.2.7 validates_inclusion_of and validates_exclusion_of

These twin 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 politically incorrect 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: %w( m f ), message: '- O RLY?'
class Account < ActiveRecord::Base
  validates_exclusion_of :username, in: %w( admin superuser ),
                         message: ', huh? Borat says "Naughty, naughty!"'

Notice that in the last example I introduced usage of the :message option, common to all validation methods, to customize the error message constructed and added to the errors object when the validation fails. We’ll cover the default error messages and how to effectively customize them a little further along in the chapter.

8.2.8 validates_length_of

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 Constraint Options

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 :username, within: 5..20

To specify an exact length of an attribute, use the :is option:

class Account < ActiveRecord::Base
  validates_length_of :account_number, is: 16
end Error Message Options

Rails gives you the capability to generate detailed error messages for validates_length_of via the :too_long, :too_short, and :wrong_length options. Use %{count} 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 %{count} characters long"

8.2.9 validates_numericality_of

The somewhat clumsily named validates_numericality_of method is used to ensure that an attribute can only hold a numeric value.

The :only_integer option lets you further specify that the value should only be an integer value and defaults to false.

class Account < ActiveRecord::Base
  validates_numericality_of :account_number, only_integer: true

The :even and :odd options do what you would expect and are useful for things like, I don’t know, checking electron valences. (Actually, I’m not creative enough to think of what you would use this validation for, but there you go.)

The following comparison options are also available:

  • :equal_to

  • :greater_than

  • :greater_than_or_equal_to

  • :less_than

  • :less_than_or_equal_to

  • :other_than Infinity and Other Special Float Values

Interestingly, Ruby has the concept of infinity built-in. If you haven’t seen infinity before, try the following in a console:

>> (1.0/0.0)
=> Infinity

Infinity is considered a number by validates_numericality_of. Databases (like PostgreSQL) with support for the IEEE 754 standard should allow special float values like Infinity to be stored. The other special values are positive infinity (+INF), negative infinity (-INF), and not-a-number (NaN). IEEE 754 also distinguishes between positive zero (+0) and negative zero (-0). NaN is used to represent results of operations that are undefined.

8.2.10 validates_presence_of

One of the more common validation methods, validates_presence_of, is used to denote mandatory attributes. This method checks whether the attribute is blank using the blank? method, defined on Object, which returns true for values that are nil or a blank string "".

class Account < ActiveRecord::Base
  validates_presence_of :username, :email, :account_number

A common mistake is to use validates_presence_of with a boolean attribute, like the backing field for a check box. If you want to make sure that the attribute is true, use validates_acceptance_of instead.

The boolean value false is considered blank, so if you want to make sure that only true or false values are set on your model, use the following pattern:

validates_inclusion_of :protected, in: [true, false] Validating the Presence and/or Existence of Associated Objects

When you’re trying to ensure that an association is present, pass validates_presence_of 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).

Many developers try to use this validation with the intention of ensuring that associated objects actually exist in the database. Personally, I think that would be a valid use case for an actual foreign-key constraint in the database, but if you want to do the check in your Rails code then emulate the following example:

class Timesheet < ActiveRecord::Base
  belongs_to :user
  validates_presence_of :user_id
  validate :user_exists


  def user_exists
    errors.add(:user_id, "doesn't exist") unless User.exists?(user_id)

Without a validation, if your application violates a database foreign key constraint, you will get an Active Record exception.

8.2.11 validates_uniqueness_of

The validates_uniqueness_of method is Active Record-specific and 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 at validation time. If any record is returned when this method does its query, the validation fails.

class Account < ActiveRecord::Base
  validates_uniqueness_of :username

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]

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).

With the addition of support for PostgreSQL array columns in Rails 4, the validates_uniqueness_of method can be used to validate that all items in the array are unique. Enforcing Uniqueness of Join Models

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

class Registration < ActiveRecord::Base
  belongs_to :student
  belongs_to :course

class Course < ActiveRecord::Base
  has_many :registrations
  has_many :students, through: :registrations

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"

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.” Limit Constraint Lookup

As of Rails 4, you can specify criteria that constrains a uniqueness validation against a set of records by setting the :conditions option.

To illustrate, let’s assume we have an article that requires titles to be unique against all published articles in the database. We can achieve this using validates_uniqueness_of by doing the following:

class Article < ActiveRecord::Base
  validates_uniqueness_of :title,
    conditions: -> { where.not(published_at: nil) }

When the model is saved, Active Record will query for title against all articles in the database that are published. If no results are returned, the model is valid.

8.2.12 validates_with

All of the validation methods we’ve covered so far are essentially local to the class in which they are used. If you want to develop a suite of custom, reusable validation classes, then you need a way to apply them to your models, and that is exactly what the validates_with method enables you to do.

To implement a custom validator, extend ActiveRecord::Validator and implement the validate method. The record being validated is available as record, and you manipulate its errors hash to log validation errors.

The following examples, from Ryan Daigle’s excellent post2 on this feature, demonstrate a reusable email field validator:

class EmailValidator < ActiveRecord::Validator
  def validate()
    record.errors[:email] << "is not valid" unless
      record.email =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/

class Account < ActiveRecord::Base
  validates_with EmailValidator

The example assumes the existence of an email attribute on the record. If you need to make your reusable validator more flexible, you can access validation options at runtime via the options hash, like this:

class EmailValidator < ActiveRecord::Validator
  def validate()
    email_field = options[:attr]
    record.errors[email_field] << "is not valid" unless
      record.send(email_field) =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/

class Account < ActiveRecord::Base
  validates_with EmailValidator, attr: :email

8.2.13 RecordInvalid

Whenever you do so-called bang operations (such as save!) 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
  • + Share This
  • 🔖 Save To Your Account