Home > Articles

This chapter is from the book

This chapter is from the book

8.4 Conditional Validation

Since validation methods are based on the Active Model Callback API, they all accept :if and :unless options to determine at runtime (not during class definition) whether the validation needs to be run or not.

The following three types of arguments can be supplied as parameters:

  • 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 evaling statements is relatively slow.

  • Proc A block of code to be instance_evaled, so that self is the current record. Perhaps the most elegant choice for one-line conditionals.

validates_presence_of :approver, if: -> { approved? && !legacy? }

8.4.1 Usage and Considerations

A primary use case for conditional validation is when 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.

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 refactor it to make it a little dryer using the with_options method that Active Support mixes into Object.

with_options if: :password_required? do |user|
  user.validates_presence_of :password
  user.validates_presence_of :password_confirmation
  user.validates_length_of :password, within: 4..40
  user.validates_confirmation_of :password
end

The example validations check for the two cases when a (plaintext) password field should be required in order for the model to be valid.

def password_required?
  encrypted_password.blank? || !password.blank?
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 when the user is attempting to reset a password.

8.4.2 Validation Contexts

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.

class Report < ActiveRecord::Base
  validates_presence_of :name, on: :publish
end

class ReportsController < ApplicationController
  expose(:report)

  # POST /reports/1/publish
  def publish
    if report.valid? :publish
      redirect_to report, notice: "Report published"
    else
      flash.now.alert = "Can't publish unnamed reports!"
      render :show
    end
  end
end

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.