Tip: Conditional Validations for ActiveRecord
Is isn’t always desirable, or even necessary, to validate every attribute in a given model with every save. For example, I recently had to build out a tabbed interface for editing products. Normally, when a user would press save, with the controller code calling update_attributes, errors would appear for attributes outside the current tab.
The need for intelligent, conditional validation begat the following module, which can be included in any ActiveRecord class:
module SelectiveAttributeValidatable def self.included(base) base.send(:include, InstanceMethods) base.class_eval do alias_method_chain :update_attributes, :check end end module InstanceMethods # Store the set of attributes passed in to #update_attributes. def update_attributes_with_check(attrs) @attrs_to_validate = attrs.keys.map(&:to_s) update_attributes_without_check(attrs) end # Returns true if a given attribute was passed in to #update_attributes. def should_validate?(attr) if self.new_record? attrs_to_validate_on_create.include?(attr) else @attrs_to_validate.blank? || @attrs_to_validate.include?(attr) || self.status == 'ENABLED' end end # This method must be overwritten in the class # where this module is included. Specify whichever # attributes you want validated on create: # ['name', 'sku'] def attrs_to_validate_on_create [] end # Allows the should_validate callback to be thrown. def method_missing(method, *args) if method.to_s =~ /^should_validate_([_\w]+)[?]/ return should_validate?($1) else super end end end end
After including this module, you’ll need to add conditions to your validations, as follows:
validates_presence_if :description, :if => :should_validate_description? validates_numericality_of :age, :if => :should_validate_age?
Once you’ve implemented the conditions on your validation, any call to update_attributes will validate only the attributes included in that call.
Caveat
You probably want to make sure that your objects are eventually valid. In my case, this entails storing an enabled/disabled state on the object. This way, I can specify that when the object is enabled, all of the validations should be in effect.