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