Chapter 8. ActiveRecord Validations

 

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.

Finding Errors

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

  1. Clear the errors collection.

  2. Run validations.

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

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

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

Error Message

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

The accept Option

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.

validates_associated

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

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

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.

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.

validates_inclusion_of and validates_exclusion_of

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.

validates_existence_of

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. And validates_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.

validates_format_of

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

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

Error Message Options

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

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

validates_presence_of

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

Validating the Presence of Associated Objects

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

validates_uniqueness_of

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

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

RecordInvalid

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

Common Validation Options

The following options apply to all of the validation methods.

:allow_nil

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.

:if

The :if option is covered in the next section, “Conditional Validation.”

:message

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

:on

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.

Conditional Validation

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:

  • SymbolThe name of a method to invoke as a symbol. This is probably the most common option, and offers the best performance.

  • StringA 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.

  • BlockA proc to be call’d. Perhaps the most elegant choice for one-line conditionals.

Usage and Considerations

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.

Working with the Errors Object

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.

Manipulating the Errors Collection

Some methods are provided to allow you to add validation errors to the collection manually and alter the state of the Errors collection.

add_to_base(msg)

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.

add(attribute, msg)

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.

clear

As you might expect, the clear method clears the Errors collection.

Checking for Errors

It’s also possible to check the Errors object for validation failures on specific attributes with a couple of methods.

invalid?(attribute)

Returns true or false depending on whether there are validation errors associated with attribute.

on(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.

Custom Validation

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.

Skipping Validations

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.

Conclusion

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.

Reference

1.

If you need to validate addresses try the plugin at http://code.dunae.ca/validates_email_format_of.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.186.143