I have bought this wonderful machine—a computer. Now I am rather an authority on gods, so I identified the machine—it seems to me to be an Old Testament god with a lot of rules and no mercy.
—Joseph Campbell
The Validations API in Active Model, along with its supplementary functionality in Active Record, allows you to declaratively define valid states for your model objects. The validation methods hook into the life cycle of an Active Record 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 also explore how those validation methods interact with your model’s attributes and how the built-in error-messaging system can be used effectively in your application’s user interface to provide descriptive feedback.
Finally, we’ll cover how to use Active Model’s validation functionality in your own, non–Active Record classes.
Validation problems are also known as (drumroll, please...) errors! Every Active Record model object contains a collection of errors, accessible (unsurprisingly) as the errors
attribute.
When a model object is valid, the errors
collection is empty. In fact, when you call valid?
on a model object, it takes the following three steps to find errors (slightly simplified):
1. Clears the errors
collection
2. Runs validations
3. Returns whether the model’s errors
collection is now empty or not
If the errors
collection ends up empty, the object is valid. In cases where you have to write actual validation logic yourself, you mark an object invalid by adding items to the errors
collection using its add
methods. Simple as that.
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 Active Record classes. Unless otherwise noted, all 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.
New to Rails 4, 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.
1 class Account < ActiveRecord::Base
2 validates_absence_of :something_unwanted
3 end
When the validates_absence_of
validation fails, an error message is stored in the model object reading “attribute must be blank.”
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.
1 class Account < ActiveRecord::Base
2 validates_acceptance_of :privacy_policy, :terms_of_service
3 end
You can use this validation with or without a boolean columns 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. Mind you, not accepting the term would prevent creation of the record, but it’s good to know what is supported.
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.
1 class Cancellation < ActiveRecord::Base
2 validates_acceptance_of :account_cancellation, accept: 'YES'
3 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.
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 their new child records on save.
Suggestion
You probably don’t need to use this particular validation nowadays since has_many
associations default to validate: true
. Additionally, note that one of the implications of that default is that setting validate: true
carelessly on a belongs_to
association can cause infinite loop problems.
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
.
Tim Says ...
It’s possible to get similar behavior by using a combination of the :autosave
and :validate
options on a has_many
.
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 addresses 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:
1 class Account < ActiveRecord::Base
2 validates_confirmation_of :password
3 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.
1 = form_for account do |f|
2 = f.label :login
3 = f.text_field :login
4 = f.label :password
5 = f.password_field :password
6 = f.label :password_confirmation
7 = f.password_field :password_confirmation
8 = f.submit
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. Notice that parameters for the model instance (record
), the name of the attribute as a symbol, and the value to check are passed as block parameters. 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:
1 class Invoice < ActiveRecord::Base
2 validates_each :supplier_id, :purchase_order do |record,
attr, value|
3 record.errors.add(attr) unless PurchasingSystem.
validate(attr, value)
4 end
5 end
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:
1. Check out the excellent http://rubular.com
if you need help composing Ruby regular expressions.
1 class Person < ActiveRecord::Base
2 validates_format_of :email,
3 with: /A([^@s]+)@((?:[-a-z0-9]+.)+[a-z]{2,})z/
4 end
By the way, that example is not an RFC-compliant email address format checker.2
2. If you need to validate email addresses, try the plugin at https://github.com/spectator/validates_email
Courtenay Says ...
Regular expressions are awesome but can get very complex, particularly when validating domain names or email addresses. You can use #{}
inside regular expressions, so split up your regex into chunks like this:
validates_format_of :name, with:
/A((localhost)|#{DOMAIN}|#{NUMERIC_IP})#{PORT}z/
That expression is pretty straightforward and easy to understand. The constants themselves are not so easy to understand but easier than if they were all jumbled in together:
1 PORT = /(([:]d+)?)/
2 DOMAIN = /([a-z0-9-]+.?)*([a-z0-9]{2,}).[a-z]{2,}/
3 NUMERIC_IP = /(?>(?:1?d?d|2[0-4]d|25[0-5]).){3}
4 (?:1?d?d|2[0-4]d|25[0-5])(?:/(?:[12]?d|3[012])|-(?>(?:1?d?d|
5 2[0-4]d|25[0-5]).){3}(?:1?d?d|2[0-4]d|25[0-5]))?/
Lark Says ...
I’ll take your readability, Courtenay, and raise you test isolation. Your regular expression should itself be in a constant so you can test it.
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:
1 class Person < ActiveRecord::Base
2 validates_inclusion_of :gender, in: %w( m f ), message: 'O RLY?'
3 ...
4
5 class Account < ActiveRecord::Base
6 validates_exclusion_of :username, in: %w( admin superuser ),
7 message: 'Borat says "Naughty, naughty!"'
8 ...
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 later in the chapter.
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.
1 class Account < ActiveRecord::Base
2 validates_length_of :login, minimum: 5
3 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:
1 class Account < ActiveRecord::Base
2 validates_length_of :username, within: 5..20
3 end
To specify an exact length of an attribute, use the :is
option:
1 class Account < ActiveRecord::Base
2 validates_length_of :account_number, is: 16
3 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 %{count}
in your custom error message as a placeholder for the number corresponding to the constraint.
1 class Account < ActiveRecord::Base
2 validates_length_of :account_number, is: 16,
3 wrong_length: "should be %{count} characters long"
4 end
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.
1 class Account < ActiveRecord::Base
2 validates_numericality_of :account_number, only_integer: true
3 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:
• :greater_than
• :greater_than_or_equal_to
• :less_than
• :less_than_or_equal_to
• :other_than
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.
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 ("")
.
1 class Account < ActiveRecord::Base
2 validates_presence_of :username, :email, :account_number
3 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]
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:
1 class Timesheet < ActiveRecord::Base
2 belongs_to :user
3 validates_presence_of :user_id
4 validate :user_exists
5
6 protected
7
8 def user_exists
9 errors.add(:user_id, "doesn't exist") unless User.exists?(user_id)
10 end
11 end
Without a validation, if your application violates a database foreign key constraint, you will get an Active Record exception.
The validates_uniqueness_of
method, also exclusive to Active Record, 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.
1 class Account < ActiveRecord::Base
2 validates_uniqueness_of :username
3 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).
1 class Address < ActiveRecord::Base
2 validates_uniqueness_of :line_two, scope: [:line_one, :city, :zip]
3 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. PostgreSQL array columns are covered in detail in Chapter 9, “Advanced Active Record.”
Tim Says ...
This validation is not foolproof, due to a potential race condition between the SELECT
query that checks for duplicates and the INSERT
or UPDATE
that persists the record. An Active Record exception could be generated as a result, so be prepared to handle that failure in your controller. I recommend that you use a unique index constraint in the database if you absolutely must make sure that a column value is unique.
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:
1 class Student < ActiveRecord::Base
2 has_many :registrations
3 has_many :courses, through: :registrations
4 end
5
6 class Registration < ActiveRecord::Base
7 belongs_to :student
8 belongs_to :course
9 end
10
11 class Course < ActiveRecord::Base
12 has_many :registrations
13 has_many :students, through: :registrations
14 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:
1 class Registration < ActiveRecord::Base
2 belongs_to :student
3 belongs_to :course
4
5 validates_uniqueness_of :student_id, scope: :course_id,
6 message: "can only register once per course"
7 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.”
Tim Says ...
Astute readers will notice that the validation was on student_id
but the error message references “Student.” Rails special cases this to do what you mean.
As of Rails 4, one can specify criteria that constraints 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:
1 class Article < ActiveRecord::Base
2 validates_uniqueness_of :title,
3 conditions: -> { where.not(published_at: nil) }
4 ...
5 end
When the model is saved, Active Record will query for the title against all articles in the database that are published. If no results are returned, the model is valid.
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 what the validates_with
method allows 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 post3 on this feature, demonstrate a reusable email field validator:
3. http://ryandaigle.com/articles/2009/8/11/what-s-new-in-edge-rails-independent-model-validators
1 class EmailValidator < ActiveRecord::Validator
2 def validate()
3 record.errors[:email] << "is not valid" unless
4 record.email =~ /A([^@s]+)@((?:[-a-z0-9]+.)+[a-z]
{2,})z/
5 end
6 end
7
8 class Account < ActiveRecord::Base
9 validates_with EmailValidator
10 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:
1 class EmailValidator < ActiveRecord::Validator
2 def validate()
3 email_field = options[:attr]
4 record.errors[email_field] << "is not valid" unless
5 record.send(email_field) =~ /A([^@s]+)@((?:[-a-z0-9]+.)+[a-z]{2,})z/
6 end
7 end
8
9 class Account < ActiveRecord::Base
10 validates_with EmailValidator, attr: :email
11 end
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
The following options apply to all the validation methods.
In some cases, you only want to trigger a validation if a value is present—in other words, the attribute is optional. There are two options that provide this functionality.
The :allow_blank
option skips validation if the value is blank according to the blank?
method. Similarly, the :allow_nil
option skips the validation if the value of the attribute is nil
; it only checks for nil
, and empty strings ""
are not considered nil—they are considered blank.
The :if
and :unless
options are 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 the validation methods accept a :message
option so that you can override the default error message format.
1 class Account < ActiveRecord::Base
2 validates_uniqueness_of :username, message: "is already taken"
3 end
The default English locale file in ActiveModel defines most of the standard error message templates.
inclusion: "is not included in the list"
exclusion: "is reserved"
invalid: "is invalid"
confirmation: "doesn't match %{attribute}"
accepted: "must be accepted"
empty: "can't be empty"
blank: "can't be blank"
present: "must be blank"
too_long: "is too long (maximum is %{count} characters)"
too_short: "is too short (minimum is %{count} characters)"
wrong_length: "is the wrong length (should be %{count} characters)"
not_a_number: "is not a number"
not_an_integer: "must be an integer"
greater_than: "must be greater than %{count}"
greater_than_or_equal_to: "must be greater than or equal to %{count}"
equal_to: "must be equal to %{count}"
less_than: "must be less than %{count}"
less_than_or_equal_to: "must be less than or equal to %{count}"
other_than: "must be other than %{count}"
odd: "must be odd"
even: "must be even"
The default messages only use the count
variable for interpolation, where appropriate, but model
, attribute
, and value
are always available.
validates_uniqueness_of username, message: "%{value} is already registered"
By default, validations are run on save (both create and update operations). If you need to do so, you can limit a given validation to just one of those operations by passing the :on
option either :create
or :update
.
Assuming that your application does not support changing emails, one good use for on: :create
might be in conjunction with validates_uniqueness_of
, since checking uniqueness with a query on large datasets can be time consuming.
1 class Account < ActiveRecord::Base
2 validates_uniqueness_of :email, on: :create
3 end
New to Rails 4 is the :strict
validation option. Setting :strict
to true
causes an exception—ActiveModel::StrictValidationFailed
—to be raised when a model is invalid.
1 class Account < ActiveRecord::Base
2 validates :email, presence: { strict: true }
3 end
To override the type of exception raised on error, pass the custom exception to the :strict
option.
Since all validation methods are implemented via the Active Model Callback API, they also accept :if
and :unless
options to determine at runtime (and not during the class definition) whether the validation needs to be run or not. The following three types of arguments can be supplied as:if
and :unless
options:
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 evaluating statements is relatively slow.
Proc A block of code to be evaluated with instance_eval
, so that self
is the current record. Perhaps the most elegant choice for one-line conditionals.
validates_presence_of :approver, if: -> { approved? && !legacy? }
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 involves the User
(or Person
) model, used for login and authentication.
1 validates_presence_of :password, if: :password_required?
2 validates_presence_of :password_confirmation, if: :password_required?
3 validates_length_of :password, within: 4..40, if: :password_required?
4 validates_confirmation_of :password, if: :password_required?
This code is not DRY (meaning that it is repetitive). You can refactor it to make it a little dryer using the with_options
method that Rails mixes into Object
.
1 with_options if: :password_required? do |user|
2 user.validates_presence_of :password
3 user.validates_presence_of :password_confirmation
4 user.validates_length_of :password, within: 4..40
5 user.validates_confirmation_of :password
6 end
All the example validations check for the two cases when a (plaintext) password field should be required in order for the model to be valid.
1 def password_required?
2 encrypted_password.blank? || !password.blank?
3 end
The first case is if the encrypted_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 his or her password.
Another way to accomplish conditional validation leverages support for validation contexts. Declare a validation and pass the name of an application-specific validation context as the value of the :on
option. That validation will now only be checked when explicitly invoked using record.valid?(context_name)
.
Consider the following example involving a report generation app. Saving a report without a name is fine, but publishing one without a name is not.
1 class Report < ActiveRecord::Base
2 validates_presence_of :name, on: :publish
3 end
4
5 class ReportsController < ApplicationController
6 expose(:report)
7
8 # POST /reports/1/publish
9 def publish
10 if report.valid? :publish
11 redirect_to report, notice: "Report published"
12 else
13 flash.now.alert = "Can't publish unnamed reports!"
14 render :show
15 end
16 end
17 end
Introduced in Rails 3, the validates
method identifies an attribute and accepts options that correspond to the validators we’ve already covered in the chapter. Using validates
can tighten up your model code nicely.
1 validates :username, presence: true,
2 format: { with: /[A-Za-z0-9]+/ },
3 length: { minimum: 3 },
4 uniqueness: true
The following options are available for use with the validates
method.
absence: true Alias for validates_absence_of
. Supply additional options by replacing true
with a hash.
validates :unwanted, absence: { message: "You shouldn't have set that" }
acceptance: true Alias for validates_acceptance_of
, typically used with check boxes that indicate acceptance of terms. Supply additional options by replacing true
with a hash.
validates :terms, acceptance: { message: 'You must accept terms.' }
confirmation: true Alias for validates_confirmation_of
, typically used to ensure that email and password confirmation fields match up correctly. Supply additional options by replacing true
with a hash.
validates :email, confirmation: { message: 'Try again.' }
exclusion: { in: [1,2,3] } Alias for validates_exclusion_of
. If your only option is the array to exclude against, you can shorten the syntax further by supplying an array as the value.
validates :username, exclusion: %w(admin superuser)
format: { with: /.*/ } Alias for validates_format_of
. If your only option is the regular expression, you can shorten the syntax further by making it the value like the following:
format: /[A-Za-z0-9]+/
inclusion: { in: [1,2,3] } Alias for validates_inclusion_of
. If your only option is the inclusion array, you can shorten the syntax further by making the array the value.
validates :gender, inclusion: %w(male female)
length: { minimum: 0, maximum: 1000 } Alias for validates_length_of
. If your only options are minimum and maximum lengths, you can shorten the syntax further by supplying a Ruby range as the value.
validates :username, length: 3..20
numericality: true Alias for validates_numericality_of
. Supply additional options by replacing true
with a hash.
validates :quantity, numericality: { message: 'Supply a number.' }
presence: true Alias for validates_presence_of
. Supply additional options by replacing true
with a hash.
validates :username, presence: { message: 'How do you expect to login?' }
uniqueness: true Alias for validates_uniqueness_of
. Supply additional options by replacing true
with a hash.
validates :quantity, uniqueness: { message: "You're SOL on that login choice, buddy!" }
When the existing declarative validation macros are not enough for your application needs, Rails gives you a few custom techniques.
Rails has the ability to add custom validation macros (available to all your model classes) by extending ActiveModel::EachValidator
.
The following example is silly but demonstrates the functionality nicely.
1 class ReportLikeValidator < ActiveModel::EachValidator
2 def validate_each(record, attribute, value)
3 unless value["Report"]
4 record.errors.add(attribute, 'does not appear to be a Report')
5 end
6 end
7 end
Now that your custom validator exists, it is available to use with the validates
macro in your model.
1 class Report < ActiveRecord::Base
2 validates :name, report_like: true
3 end
The key :report_like
is inferred from the name of the validator class, which in this case was ReportLikeValidator
.
You can receive options via the validates
method by adding an initializer
method to your custom validator class. For example, let’s make ReportLikeValidator
more generic.
1 class LikeValidator < ActiveModel::EachValidator
2 def initialize(options)
3 @with = options[:with]
4 super
5 end
6
7 def validate_each(record, attribute, value)
8 unless value[@with]
9 record.errors.add(attribute, "does not appear to be like #{@with}")
10 end
11 end
12 end
Our model code would change to the following:
1 class Report < ActiveRecord::Base
2 validates :name, like: { with: "Report" }
3 end
This technique involves inheriting from ActiveModel::Validator
and implementing a validate
method that takes the record to validate.
I’ll demonstrate with a really wicked example.
1 class RandomlyValidator < ActiveModel::Validator
2 def validate(record)
3 record.errors[:base] << "FAIL #1" unless first_hurdle(record)
4 record.errors[:base] << "FAIL #2" unless second_hurdle(record)
5 record.errors[:base] << "FAIL #3" unless third_hurdle(record)
6 end
7
8 private
9
10 def first_hurdle(record)
11 rand > 0.3
12 end
13
14 def second_hurdle(record)
15 rand > 0.6
16 end
17
18 def third_hurdle(record)
19 rand > 0.9
20 end
21 end
Use your new custom validator in a model with the validates_with
macro.
1 class Report < ActiveRecord::Base
2 validates_with RandomlyValidator
3 end
A validate
instance method might be the way to go if you want to check the state of your object holistically and keep the code for doing so inside of the model class itself. (This is an older technique that I can’t fully endorse; it adds complexity to your model class unnecessarily, given how easy it is to create custom validator classes.)
For example, 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:
1 class CompletelyLameTotalExample < ActiveRecord::Base
2 def validate
3 if total != (attr1 + attr2 + attr3)
4 errors[:total] << "The total doesn't add up!"
5 end
6 end
7 end
You can alternatively add an error message to the whole object instead of just a particular attribute using the :base
key, like this:
errors[:base] << "The total doesn't add up!"
Remember, the way to mark an object as invalid is to add to its Errors
object. The return value of a custom validation method is not used.
The methods update_attribute
and update_column
don’t invoke validations, yet their companion method update
does. 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
or update_column
methods. It can easily persist your model objects in invalid states.
Some methods are provided to allow you to add validation errors to the collection manually and alter the state of the Errors
hash.
Adds an error message related to the overall 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.
As you might expect, the clear
method clears the Errors
collection.
It’s also possible to check the Errors
object for validation failures on specific attributes by just using square brackets notation. An array is always returned, but it is empty when there aren’t any validation errors for the attribute specified.
>> user.errors[:login]
=> ["zed is already registered"]
>> user.errors[:password]
=> []
Alternatively, one could also access full error messages for a specific attribute using the full_messages_for
method. Just like accessing validation failures for attributes using bracket notation, an array is always returned.
>> user.errors.full_messages_for(:email)
=> ["Email can't be blank"]
Even though validations are declarative code, if you’re doing TDD, then you’ll want to specify them before writing them. Luckily, Thoughtbot’s Shoulda Matchers library4 contains a number of matchers designed to easily test validations.
4. https://github.com/thoughtbot/shoulda-matchers
1 describe Post do
2 it { should validate_uniqueness_of(:title) }
3 it { should validate_presence_of(:body).with_message(/wtf/) }
4 it { should validate_presence_of(:title) }
5 it { should validate_numericality_of(:user_id) }
6 end
7
8 describe User do
9 it { should_not allow_value("blah").for(:email) }
10 it { should_not allow_value("b lah").for(:email) }
11 it { should allow_value("[email protected]").for(:email) }
12 it { should allow_value("[email protected]").for(:email) }
13 it { should ensure_length_of(:email).is_at_least(1).is_at_most(100) }
14 it { should ensure_inclusion_of(:age).in_range(1..100) }
15 end
In this (relatively speaking) short chapter, we covered the Active Record 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.
18.191.139.169