General validation options in Ruby

There are several common validation options:

:allow_nil

The :allow_nil option skips validation when the value being checked is nil.

class Coffee < ApplicationRecord

  validates :size, inclusion: { in: %w(small medium large),

    message: “%{value} is not a valid size” }, allow_nil: true

end

See the message documentation for all message argument options.

:allow_blank

The :allow_blank option is similar to the :allow_nil option. This option skips validation if the value of the attribute is blank?, such as nil or the empty string.

class Topic < ApplicationRecord

  validates :title, length: { is: 5 }, allow_blank: true

end

Topic.create(title: “”).valid? # => true

Topic.create(title: nil).valid? # => true

:message

As we’ve seen, the :message option allows you to specify a message to be added to the errors collection when the validation fails. If this option is not used, Active Record will use the appropriate default error messages for each validation helper. The :message option accepts a String or a Proc.

The String value in :message can optionally contain any of %{value}, %{attribute} and %{model} which will be dynamically replaced when the validation fails. This substitution is performed if the I18n gem is used and the insertion point must match exactly, no spaces are allowed.

The value of Proc in :message is given with two arguments: the object to be checked and a hash with keys :model, :attribute and :value.

classPerson < ApplicationRecord

  # Hardcoded message

  validates :name, presence: { message: “must be given please” }

  # Message with value with dynamic attribute. %{value} will be replaced

  # the actual value of the attribute. %{attribute} and %{model} are also available.

  validates :age, numericality: { message: “%{value} seems wrong” }

  #Proc

  validates :username,

    uniqueness: {

      # object = person object being validated

      # data = { model: “Person”, attribute: “Username”, value: <username> }

      message: ->(object, data) do

        “Hey #{object.name}!, #{data[:value]} is taken already! Try again #{Time.zone.tomorrow}”

      end

    }

end

:on

The :on option allows you to specify when validation should occur. The standard behavior for all built-in validation helpers is to run on save (both when a new record is created and when it is updated). If you want to change this, use on: :create to run validation only when a new post is created, or on: :update to run validation when a post is updated.

classPerson < ApplicationRecord

  # it will be possible to update an email with a duplicate value

  validates :email, uniqueness: true, on: :create

  # it will be possible to create an entry with a non-numeric age

  validates :age, numericality: true, on: :update

  # by default (checks both on creation and update)

  validates :name, presence: true

end

on: can also be used to define a user context. User contexts must be explicitly enabled by passing the context name to valid?, invalid? or save.

classPerson < ApplicationRecord

  validates :email, uniqueness: true, on: :account_setup

  validates :age, numericality: true, on: :account_setup

end

person = Person.new

person.valid?(:account_setup) will do both validations without saving the model. And person.save(context: :account_setup) validates person in the context of account_setup before saving. When explicitly included, the model is validated only by validations of this context only and by validations without context.

Strict validations

It is also possible to define validations as strict so that they call ActiveModel::StrictValidationFailed when the object is not valid.

classPerson < ApplicationRecord

  validates :name, presence: { strict: true }

end

Person.new.valid? # => ActiveModel::StrictValidationFailed: Name can’t be blank

It is also possible to pass your own exception to the :strict option.

classPerson < ApplicationRecord

  validates :token, presence: true, uniqueness: true, strict: TokenGenerationException

end

Person.new.valid? # => TokenGenerationException: Token can’t be blank

Conditional Validation

Sometimes it makes sense to validate an object only if a given predicate is met. This can be done using the :if and :unless options, which take a character, Proc, or Array. The :if option can be used if you want to specify when validation should occur. If you need to specify when validation should not occur, use the :unless option.

Using a symbol with :if and :unless

You can bind the :if and :unless options to a symbol corresponding to the name of the method that will be called before validation. This is the most commonly used option.

classOrder < ApplicationRecord

  validates :card_number, presence: true, if: :paid_with_card?

  def paid_with_card?

    payment_type == “card”

  end

end

Using Proc with :if and :unless

Finally, you can bind :if and :unless to the Proc object that will be called. Using the Proc object makes it possible to write an inline condition instead of a separate method. This option is best for single line code.

class Account < ApplicationRecord

  validates :password, confirmation: true,

    unless: Proc. new { |a| a.password.blank? }

end

Grouping conditional validations

Sometimes it’s useful to have multiple validations with the same condition. This is easily achieved using with_options.

class User<ApplicationRecord

  with_options if: :is_admin? do |admin|

    admin.validates :password, length: { minimum: 10 }

    admin.validates :email, presence: true

  end

end

All validations inside with_options will automatically be passed an if: :is_admin? condition.

Combining Validation Conditions

On the other hand, an array can be used when multiple conditions determine whether validation should occur. Moreover, both :if: and :unless can be used in the same validation.

class Computer < ApplicationRecord

  validates :mouse, presence: true,

                    if: [Proc.new { |c| c.market.retail? }, :desktop?],

                    unless: Proc.new { |c| c.trackpad.present? }

end

Validation will only be performed when all of the :if conditions and none of the :unless conditions evaluate to true.

Performing Your Own Validations

When the built-in validation helpers are not enough for your needs, you can write your own validators or validation methods.

Own validators

Custom validators are classes that inherit from ActiveModel::Validator. These classes must implement a validate method that takes an entry as an argument and performs validation on it. The custom validator is called using the validates_with method.

class MyValidator < ActiveModel::Validator

  def validate(record)

    unless record.name.starts_with? ‘X’

      record.errors[:name] << ‘Need a name starting with X please!’

    end

  end

end

class person

  include ActiveModel::Validations

  validates_with MyValidator

end

The easiest way to add custom validators to validate individual attributes is to inherit from ActiveModel::EachValidator. In this case, the custom validator class must implement the validate_each method, which takes three arguments: entry, attribute, and value. These will be the corresponding instance, the attribute to be checked, and the value of the attribute in the passed instance:

Leave a Reply

Your email address will not be published.