Understanding ActiveRecord Validations with Ruby on Rails

What are validations and how are they useful?
Validations are essential to a Rails application. They allow us to control the information that is being saved into our database. They are given to us through Active Record and allow us to be thoughtful about how we save data. There are many ways that you can control database information. You can use client-side validations and controller-level validations, but the Rails team favors model-level validations for a multitude of reasons. They are database agnostic, meaning that they can be used with various systems and are not customized for a single system. They cannot be bypassed by users. They are convenient to test and maintain.
Validations are triggered on .save
or .valid?
. In other words, validations are only triggered when we are attempting to persist data to the database. Active Record is doing a lot for us behind the scenes. It gives us a method called new_record?
that checks the database to see if the record exists in our database and will return true if it’s new. Below is an example of the console output testing this method:
$ rails console>> b = Book.new(name: "A Tale of Two Cities")=> #<Book id: nil, name: "A Tale of Two Cities", created_at: nil, updated_at: nil>>> b.new_record?=> true>> b.save=> true>> b.new_record?=> false
Creating and updating objects will not trigger the validation until a save attempt is made in which case the database will return an invalid response and will not persist the data.
The following methods all trigger validations: (note the bang versions will raise an exception if invalid while the others will simply not save the data)
create
create!
save
save!
update
update!
In this blog, we will explore:
- How to use the built in validations provided by Active Record
- Custom Validations
- Using the errors that validations give us
The built in validations are very useful. Out of the box we are given the following validations (each of the following links to the Ruby Guide):
- acceptance
- validates_associated
- confirmation
- exclusion
- format
- inclusion
- length
- numericality
- presence
- uniqueness
Each of these validations takes in an unlimited number of attributes so you can use a single line of code to define validations for multiple attributes.
Use Cases
Acceptance is used in specific cases where you need to determine if a User has checked a box or read some text, typically when signing up for a webpage, agreeing to terms of services, etc.. You do not have to have a field for it in your database as this validation will create a “virtual attribute” for you.
class User < ApplicationRecord validates :terms_of_service, acceptance: trueend
Validates_Associated is used when you need to ensure that a model is associated with another model. Be careful, do not use it on both sides of an association as it will cause an infinite loop.
class List < ApplicationRecord has_many :tasks validates_associated :tasks end
Confirmation is used when you need two input boxes to contain the exact same information. This is especially useful when asking for password confirmation. This creates a “virtual attribute” with _confirmation
appended to the attribute name (password_confirmation
). The check is only performed if a confirmation is present, so it is important to also validate for presence(covered later).
class User < ApplicationRecord validates :password, confirmation: true
validates :password, presence: true end
You could then use something like this in your views:
<%= text_field :user, :password %><%= text_field :user, :password_confirmation %>
Confirmation allows for a :case_sensitive
option that can be set to true
or false
which can be passed in after the validation in curly braces ({}).
class User < ApplicationRecord validates :password, confirmation: { case_sensitive: false }end
Exclusion is used to ensure that certain attributes are not included in the set. It has an option that allows you to pass :in
which lets you reserve things to disallow any one from saving data that includes certain characters.
class Account < ApplicationRecord validates :subdomain, exclusion: { in: %w(www us ca jp), message: "%{value} is reserved." }end
Format is used to determine if a value input is in the correct format to be saved to the database. It allows you to pass in an option :with
. You can also pass in a :without
option that will require that an attribute does not have that formatting.
class User < ApplicationRecord
validates :name, format: { with: /\A\w{6,10}\z/ }
end
Inclusion validates that the attribute is in a given set. It has an option of in:
that allows you to set the values that will be accepted.
class Shirt < ApplicationRecord validates :size, inclusion: { in: %w(small medium large), message: "%{value} is not a valid size" }end
Length validates that an attribute’s value is a certain length. This can be useful with passwords or to ensure that a text body is limited to a certain number of characters.
class Post < ApplicationRecord validates :title, length: { minimum: 2 } validates :body, length: { maximum: 500 }end
or
class Post < ApplicationRecord validates :username, length { minimum: 2 } validates :password, length: { in: 6..20 } validates :registration_number, length: { is: 6 }end
Numericality is used to ensure that user input is only an integer or float. You can optionally set only_integer: true
if you do not want it to be converted to a float. The only_integer
helper also provides further constraints that can be added. These are as follows:
greater_than
greater_than_or_equal_to
equal_to
less_than
less_than_or_equal_to
other_than
odd
even
class Stock < ApplicationRecord validates :stock_qty, :numericality => { :only_integer => true,
:greater_than_or_equal_to => 0 }end
Presence validates that attributes cannot be empty. This is important in new forms to ensure that nil
data is not being saved to the database. If we are saving data that is incomplete this can break forms and views later on.
class Person < ApplicationRecord validates :name, :username, :email, presence: trueend
Uniqueness validates that an attribute is not the same as an object that is already stored in the database. This is important to use for user data to ensure that a user is not signing up multiple times or that username’s are not duplicated.
class User < ApplicationRecord validates :email, uniqueness: true validates :username, uniqueness: true end
Uniqueness has a scope option that can be used to limit the check of the data.
class Holiday < ApplicationRecord validates :name, uniqueness: { scope: :year, message: "only happens once per year" }end
Custom Validations
Custom validations inherit from ActiveRecord::Validator
. When writing a custom validation we need to call to it using the validates_with
helper. These validation classes must utilize the validate
method which takes the record as an argument and performs the validation on it.
class CustomValidator < ActiveModel::Validator
def validate(record) if ! (record.valid...)
record.errors[:base] << "Custom Error message"
end end end class Post < ApplicationRecord validates_with CustomValidator end
When trying to validate specific attributes you can use the validate_each
method. The validate_each
method takes in three arguments: record, attribute, value. These belong to the instance, the attribute we are trying to validate, and the value of said attribute.
class EmailValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i record.errors[attribute] << (options[:message] || "is not an
email") end end endclass Person < ApplicationRecord validates :email, presence: true, email: trueend
Utilizing Validation Errors
Validations in Rails give us access to errors.full_messages
or errors.to_a
— both of these provide us with errors that we can show to the user to assist them in correctly adding data to the database. Each validation has a unique error that can be rendered. For example:
class Person < ApplicationRecord validates :name, presence: true, length: { minimum: 3 }end# in consoleperson = Person.newperson.valid? # => falseperson.errors[:name]# => ["can't be blank", "is too short (minimum is 3 characters)"]
These errors can be extremely useful to us. When asking users to input data we want to be sure that we are getting the correct data and that the user also understands how to properly fill out the forms we have provided them. It would be very frustrating for a user to be unable to persist data because they do not know what that data should look like. Therefore, we want to be sure to call to these errors.full_messages
in our views and re-render the form/page to the user with the error messages. We lose access to these errors on redirect so it is imperative to render the form/page again.
<% if model.errors.any? %> <div id="error_explanation "> <%= pluralize(model.errors.count, "error") %>
prohibited this from being saved:
<ul><% model.errors.full_messages.each do |msg| %>
<li><%= msg %></li> <% end %> </ul> <% end %>

I hope this was helpful in understanding the importance of validations in Rails and the many ways that they can assist in keeping that database clean and functional.
References: