Home > Articles

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
end

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
end

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

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

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/
end

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

8.2.8.1 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
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

8.2.8.2 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"
end

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
end

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

8.2.9.1 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
end

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]

8.2.10.1 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

  protected

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

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

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.

8.2.11.1 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
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.”

8.2.11.2 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) }
  ...
end

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/
  end
end

class Account < ActiveRecord::Base
  validates_with EmailValidator
end

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/
  end
end

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

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

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020