4.15. Validation

Non-valid records are frequently a source of errors in programs. With validates, Rails offers a quick and easy way of validating them. That way you can be sure that only meaningful records will find their way into your database.

Preparation

Let's create a new application for this chapter:
$ rails new shop
  [...]
$ cd shop
$ rails generate model product name 'price:decimal{7,2}' weight:integer in_stock:boolean expiration_date:date
  [...]
$ rake db:migrate
  [...]
$

The Basic Idea

For each model, there is a matching model file in the directory app/models/. In this Ruby code, we can not only define database dependencies, but also implement all validations. The advantage: Every programmer knows where to find it.
Without any validation, we can create an empty record in a model without a problem:
$ rails console
Loading development environment (Rails 4.0.0)
>> Product.create
   (0.1ms)  begin transaction
  SQL (2.6ms)  INSERT INTO "products" ("created_at", "updated_at") VALUES (?, ?)  [["created_at", Tue, 16 Jul 2013 17:50:34 UTC +00:00], ["updated_at", Tue, 16 Jul 2013 17:50:34 UTC +00:00]]
   (3.4ms)  commit transaction
=> #<Product id: 1, name: nil, price: nil, weight: nil, in_stock: nil, expiration_date: nil, created_at: "2013-07-16 17:50:34", updated_at: "2013-07-16 17:50:34">
>> puts Product.first.to_yaml
  Product Load (0.3ms)  SELECT "products".* FROM "products" ORDER BY "products"."id" ASC LIMIT 1
--- !ruby/object:Product
attributes:
  id: 1
  name: 
  price: 
  weight: 
  in_stock: 
  expiration_date: 
  created_at: 2013-07-16 17:50:34.791368000 Z
  updated_at: 2013-07-16 17:50:34.791368000 Z
=> nil
>> exit
$
But in practice, this record with no content doesn't make any sense. A Product needs to have a name and a price. That's why we can define validations in ActiveRecord. Then you can ensure as programmer that only records that are valid for you are saved in your database.
To make the mechanism easier to understand, I am going to jump ahead a bit and use the presence helper. Please fill your app/model/product.rb with the following content:
class Product < ActiveRecord::Base
  validates :name,
            presence: true

  validates :price,
            presence: true
end
Now we try again to create an empty record in the console:
$ rails console
Loading development environment (Rails 4.0.0)
>> product = Product.create
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
=> #<Product id: nil, name: nil, price: nil, weight: nil, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil>
>> 
Watch out for the rollback transaction part and the misssing id of the Product object! Rails began the transaction of creating a new record but for some reason it couldn't do it. So it had to rollback the transaction. The validation method intervened before the record was saved. So validating happens before saving.
Can we access the errors? Yes, via the method errors or with errors.messages we can look at the errors that occurred:
>> product.errors
=> #<ActiveModel::Errors:0x007fec75067a20 @base=#<Product id: nil, name: nil, price: nil, weight: nil, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil>, @messages={:name=>["can't be blank"], :price=>["can't be blank"]}>
>> product.errors.messages
=> {:name=>["can't be blank"], :price=>["can't be blank"]}
>> 
This error message was defined for a human and English-speaking user (more on this and how the errors can be translated into another language in Chapter 10, Internationalization).
Only once we assign a value to the attributes name and price, we can save the object:
>> product.name = 'Milk (1 liter)'
=> "Milk (1 liter)"
>> product.price = 0.45
=> 0.45
>> product.save
   (0.2ms)  begin transaction
  SQL (2.9ms)  INSERT INTO "products" ("created_at", "name", "price", "updated_at") VALUES (?, ?, ?, ?)  [["created_at", Tue, 16 Jul 2013 17:52:50 UTC +00:00], ["name", "Milk (1 liter)"], ["price", #<BigDecimal:7fec739394c0,'0.45E0',9(45)>], ["updated_at", Tue, 16 Jul 2013 17:52:50 UTC +00:00]]
   (2.9ms)  commit transaction
=> true
>> 

valid?

The method valid? indicates in boolean form if an object is valid. So you can check the validity already before you save:
>> product = Product.new
=> #<Product id: nil, name: nil, price: nil, weight: nil, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil>
>> product.valid?
=> false
>>

save( validate: false )

As so often in life, you can find a way around everything. If you pass the parameter :validate => false to the method save, the data of Validation is saved:
>> product = Product.new
=> #<Product id: nil, name: nil, price: nil, weight: nil, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil>
>> product.valid?
=> false
>> product.save
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
=> false
>> product.save(validate: false)
   (0.1ms)  begin transaction
  SQL (0.8ms)  INSERT INTO "products" ("created_at", "expiration_date", "in_stock", "name", "price", "updated_at", "weight") VALUES (?, ?, ?, ?, ?, ?, ?)  [["created_at", Mon, 19 Nov 2012 09:28:29 UTC +00:00], ["expiration_date", nil], ["in_stock", nil], ["name", nil], ["price", nil], ["updated_at", Mon, 19 Nov 2012 09:28:29 UTC +00:00], ["weight", nil]]
   (2.3ms)  commit transaction
=> true
>> exit
$ 

Buy the new Rails 5.1 version of this book.

I assume that you understand the problems involved here. Please only use this option if there is a good reason to do so. Otherwise you might as well do without the whole validation process.

presence

In our model product there are a few fields that must be filled in in any case. We can achieve this via presence.
app/models/product.rb
class Product < ActiveRecord::Base
  validates :name,
            presence: true

  validates :price,
            presence: true
end
If we try to create an empty user record with this, we get lots of validation errors:
$ rails console
Loading development environment (Rails 4.0.0)
>> product = Product.create
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
=> #<Product id: nil, name: nil, price: nil, weight: nil, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil>
>> product.errors.messages
=> {:name=>["can't be blank"], :price=>["can't be blank"]}
>>
Only once we have entered all the data, the record can be saved:
>> product.name = 'Milk (1 liter)'
=> "Milk (1 liter)"
>> product.price = 0.45
=> 0.45
>> product.save
   (0.2ms)  begin transaction
  SQL (6.3ms)  INSERT INTO "products" ("created_at", "expiration_date", "in_stock", "name", "price", "updated_at", "weight") VALUES (?, ?, ?, ?, ?, ?, ?)  [["created_at", Mon, 19 Nov 2012 09:30:21 UTC +00:00], ["expiration_date", nil], ["in_stock", nil], ["name", "Milk (1 liter)"], ["price", #<BigDecimal:7fc7044fad08,'0.45E0',9(45)>], ["updated_at", Mon, 19 Nov 2012 09:30:21 UTC +00:00], ["weight", nil]]
   (2.5ms)  commit transaction
=> true
>> exit
$

length

With length you can limit the length of a specific attribute. It's easiest to explain using an example. Let us limit the maximum length of the name to 20 and the minimum to 2.
app/models/product.rb
class Product < ActiveRecord::Base
  validates :name,
            presence: true,
            length: { in: 2..20 }

  validates :price,
            :presence => true
end
If we now try to save a Product with a name that consists in one letter, we get an error message:
$ rails console
Loading development environment (Rails 4.0.0)
>> product = Product.create(:name => 'M', :price => 0.45)
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
=> #<Product id: nil, name: "M", price: #<BigDecimal:7f9f3d0943c0,'0.45E0',9(45)>, weight: nil, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil>
>> product.errors.messages
=> {:name=>["is too short (minimum is 2 characters)"]}
>> exit
$

Options

length can be called with the following options.
minimum
The minimum length of an attribute. Example:
validates :name,
          presence: true,
          length: { minimum: 2 }
too_short
Defines the error message of :minimum. Default: "is too short (min is %d characters)". Example:
validates :name,
          presence: true,
          length: { minimum: 5 ,
          too_short: "must have at least %{count} characters"}

Buy the new Rails 5.1 version of this book.

For all error messages, please note Chapter 10, Internationalization.
maximum
The maximum length of an attribute. Example:
validates :name,
          presence: true,
          length: { maximum: 20 }
too_long
Defines the error message of :maximum. Default: "is too long (maximum is %d characters)". Example:
validates :name,
          presence: true,
          length: { maximum: 20 ,
          too_long: "must have at most %{count} characters" }

Buy the new Rails 5.1 version of this book.

For all error messages, please note Chapter 10, Internationalization.
is
Is exactly the specified number of characters long. Example:
validates :name,
          presence: true,
          length: { is: 8 }
:in or :within
Defines a length interval. The first number specifies the minimum number of the range and the second the maximum. Example:
validates :name,
          presence: true,
          length: { in: 2..20 }
tokenizer
You can use this to define how the attribute should be split for counting. Default: lambda{ |value| value.split(//) } (individual characters are counted). Example (for counting words):
validates :content,
          presence: true,
          length: { in: 2..20 },
          tokenizer: lambda {|str| str.scan(/\w+/)}

numericality

With numericality you can check if an attribute is a number. It's easier to explain if we use an example.
app/models/product.rb
class Product < ActiveRecord::Base
  validates :name,
            presence: true,
            length: { in: 2..20 }

  validates :price,
            presence: true

  validates :weight,
            numericality: true
end
If we now use a weight that consists of letters or contains letters instead of numbers, we will get an error message:
$ rails console
Loading development environment (Rails 4.0.0)
>> product = Product.create(name: 'Milk (1 liter)', price: 0.45, weight: 'abc') 
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
=> #<Product id: nil, name: "Milk (1 liter)", price: #<BigDecimal:7ff4a4380b30,'0.45E0',9(45)>, weight: 0, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil>
>> product.errors.messages
=> {:weight=>["is not a number"]}
>> exit
$ 

Buy the new Rails 5.1 version of this book.

You can use numericality to define the content as number even if an attribute is saved as string in the database.

Options

numericality can be called with the following options.
only_integer
The attribute can only contain an integer. Default: false. Example:
validates :weight, 
          numericality: { only_integer: true }
greater_than
The number saved in the attribute must be greater than the specified value. Example:
validates :weight, 
          numericality: { greater_than: 100 }
greater_than_or_equal_to
The number saved in the attribute must be greater than or equal to the specified value. Example:
validates :weight, 
          numericality: { greater_than_or_equal_to: 100 }
equal_to
Defines a specific value that the attribute must have. Example:
validates :weight, 
          numericality: { equal_to: 100 }
less_than
The number saved in the attribute must be less than the specified value. Example:
validates :weight, 
          numericality: { less_than: 100 }
less_than_or_equal_to
The number saved in the attribute must be less than or equal to the specified value. Example:
validates :weight, 
          numericality: { less_than_or_equal_to: 100 }
odd
The number saved in the attribute must be an odd number. Example:
validates :weight, 
          numericality: { odd: true }
even
The number saved in the attribute must be an even number. Example:
validates :weight, 
          numericality: { even: true }

uniqueness

With uniqueness you can define that the value of this attribute must be unique in the database. If you want a product in the database to have a unique name that appears nowhere else, then you can use this validation:
app/models/product.rb
class Product < ActiveRecord::Base
  validates :name,
            presence: true,
            uniqueness: true
end
If we now try to create a new Product with a name that already exists, then we get an error message:
$ rails console
Loading development environment (Rails 4.0.0)
>> Product.last
  Product Load (1.9ms)  SELECT "products".* FROM "products" ORDER BY "products"."id" DESC LIMIT 1
=> #<Product id: 4, name: "Milk (1 liter)", price: #<BigDecimal:7f90649840a8,'0.45E0',9(45)>, weight: nil, in_stock: nil, expiration_date: nil, created_at: "2012-11-19 09:30:21", updated_at: "2012-11-19 09:30:21">
>> product = Product.create(name: 'Milk (1 liter)')
   (0.1ms)  begin transaction
  Product Exists (0.1ms)  SELECT 1 AS one FROM "products" WHERE "products"."name" = 'Milk (1 liter)' LIMIT 1
   (0.1ms)  rollback transaction
=> #<Product id: nil, name: "Milk (1 liter)", price: nil, weight: nil, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil>
>> product.errors.messages
=> {:name=>["has already been taken"]}
>> exit
$

Buy the new Rails 5.1 version of this book.

The validation via uniqueness is no absolute guarantee that the attribute is unique in the database. A race condition could occur (see http://en.wikipedia.org/wiki/Race_condition). A detailled discussion of this effect would go beyond the scope of book aimed at beginners (this phenomenon is extremely rare).

Options

uniqueness can be called with the following options.
scope
Defines a scope for the uniqueness. If we had a differently structured phone number database (with just one field for the phone number), then we could use this option to specify that a phone number must only be saved once per user. Here is what it would look like:
validates :name,
          presence: true,
          uniqueness: { scope: :user_id }
case_sensitive
Checks for uniqueness of upper and lower case as well. Default: false. Example:
validates :name,
          presence: true,
          uniqueness: { case_sensitive: true }

inclusion

With inclusion you can define from which values the content of this attribute can be created. For our example, we can demonstrate it using the attribute in_stock.
app/models/product.rb
class Product < ActiveRecord::Base
  attr_accessible :expiration_date, :in_stock, :name, :price, :weight

  validates :name,
            presence: true

  validates :in_stock,
            inclusion: { in: [true, false] }
end
In our data model, a Product must be either true or false for in_stock (there must not be a nil). If we enter a different value than true or false, a validation error is returned:
$ rails console
Loading development environment (Rails 4.0.0)
>> product = Product.create(name: 'Milk low-fat (1 liter)')
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
=> #<Product id: nil, name: "Milk low-fat (1 liter)", price: nil, weight: nil, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil>
>> product.errors.messages
=> {:in_stock=>["is not included in the list"]}
>> exit
$

Buy the new Rails 5.1 version of this book.

Always remember the power of Ruby! For example, you can generate the enumerable object always live from another database. In other words, the validation is not defined statically.

Options

inclusion can be called with the following option.
message
For outputting custom error messages. Default: "is not included in the list". Example:
validates :in_stock,
          inclusion: { in: [true, false], 
                          message: 'this one is not allowed' }

Buy the new Rails 5.1 version of this book.

For all error messages, please note Chapter 10, Internationalization.

exclusion

exclusion is the inversion of the section called “inclusion”. You can define from which values the content of this attribute must not be created.
app/models/product.rb
class Product < ActiveRecord::Base
  attr_accessible :expiration_date, :in_stock, :name, :price, :weight

  validates :name,
            presence: true

  validates :in_stock,
            exclusion: { in: [nil] }
end

Buy the new Rails 5.1 version of this book.

Always remember the power of Ruby! For example, you can generate the enumerable object always live from another database. In other words, the validation does not have to be defined statically.

Options

exclusion can be called with the following option.
message
For outputting custom error messages. Example:
validates :in_stock,
          inclusion: { in: [nil], 
                          message: 'this one is not allowed' }

Buy the new Rails 5.1 version of this book.

For all error messages, please note Chapter 10, Internationalization.

format

With format you can define via a regular expression (see http://en.wikipedia.org/wiki/Regular_expression) how the content of an attribute can be structured.
With format you can for example carry out a simple validation of the syntax of an e-mail address:
validates :email, 
          format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i }

Buy the new Rails 5.1 version of this book.

It should be obvious that the e-mail address validation shown here is not complete. It is just meant to be an example. You can only use it to check the syntactic correctness of an e-mail address.

Options

validates_format_of can be called with the following options:

General Validation Options

There are some options that can be used for all validations.

allow_nil

Allows the value nil. Example:
validates :email, 
          format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i },
          allow_nil: true

allow_blank

As allow_nil, but additionally with an empty string. Example:
validates :email, 
          format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i },
          allow_blank: true

on

With on, a validation can be limited to the events create, update or safe. In the following example, the validation only takes effect when the record is initially created (during the create):
validates :email, 
          format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i },
          on: :create

if and unless

if or unless call the specified method and only execute the validation if the result of the method is true:
validates :name,
          presence: true,
          if: :today_is_monday?

def today_is_monday?
  Date.today.monday?
end
proc
:proc calls a Proc object.
validates :name,
          presence: true,
          if: Proc.new { |a| a.email == 'test@test.com' }

Writing Custom Validations

Now and then, you want to do a validation where you need custom program logic. For such cases, you can define custom validations.

Defining Validations with Your Own Methods

Let's assume you are a big shot hotel mogul and need a reservation system.
$ rails new my_hotel
  [...]
$ cd my_hotel 
$ rails generate model reservation start_date:date end_date:date room_type
  [...]
$ rake db:migrate
  [...]
$ 
Then we specify in the app/model/reservation.rb that the attributes start_date and end_date must be present in any case, plus we use the method reservation_dates_must_make_sense to make sure that the start_date is before the end_date:
class Reservation < ActiveRecord::Base
  validates :start_date,
            presence: true

  validates :end_date,
            presence: true

  validate :reservation_dates_must_make_sense

  private
  def reservation_dates_must_make_sense
    if end_date <= start_date
      errors.add(:start_date, 'has to be before the end date')
    end
  end
end
With errors.add we can add error messages for individual attributes. With errors.add_to_base you can add error messages for the whole object.
Let's test the validation in the console:
$ rails console
Loading development environment (Rails 4.0.0)
>> reservation = Reservation.new(start_date: Date.today, end_date: Date.today)
=> #<Reservation id: nil, start_date: "2012-11-19", end_date: "2012-11-19", room_type: nil, created_at: nil, updated_at: nil>
>> reservation.valid?
=> false
>> reservation.errors.messages
=> {:start_date=>["has to be before the end date"]}
>> reservation.end_date = Date.today + 1.day
=> Tue, 20 Nov 2012
>> reservation.valid?
=> true
>> reservation.save
   (0.1ms)  begin transaction
  SQL (8.7ms)  INSERT INTO "reservations" ("created_at", "end_date", "room_type", "start_date", "updated_at") VALUES (?, ?, ?, ?, ?)  [["created_at", Mon, 19 Nov 2012 14:00:50 UTC +00:00], ["end_date", Tue, 20 Nov 2012], ["room_type", nil], ["start_date", Mon, 19 Nov 2012], ["updated_at", Mon, 19 Nov 2012 14:00:50 UTC +00:00]]
   (3.4ms)  commit transaction
=> true
>> exit
$

Further Documentation

The topic validations is described very well in the official Rails documentation at http://guides.rubyonrails.org/active_record_validations.html.