Validating that at least one attribute in a list is present: validates_presence_of_any

Posted by Tim Connor Tue, 20 Nov 2007 05:10:00 GMT

Make a lib/custom_validations.rb file, with these contents

module CustomValidations
  def validates_presence_of_any(*attrs)
    options = attrs.last.is_a?(Hash) ? attrs.pop.symbolize_keys : {}
    attrs   = attrs.flatten
    send(validation_method(options[:on] || :save)) do |record|
      return if options[:if] && !evaluate_condition(options[:if], record)
      attr_list = attrs.map{|a| a.to_s.humanize}.to_sentence(:connector => "or", :skip_last_comma => true)
      record.errors.add :base, "Either #{attr_list} needed." if attrs.all?{ |attr| record.send(attr).blank?}
    end
  end
end
and then “extend CustomValidation” in your model. Then you can

 validates_presence_of_any :first_attr, :second_attr, :third_attr

Thanks to bitsweat on #caboose for help with a succinct name that follows the convention.

Doing a rails custom validator with-out using validates_each

Posted by Tim Connor Tue, 20 Nov 2007 03:01:00 GMT

I wanted to do a custom validator (forthcoming post) that validates that at least one out of a list of attributes is present (validates_presence_of_any). Unfortunately, all the examples I could find use validates_each (see here for a decent example on the topic in general), and since mine depends on the list as a whole, that doesn’t quite fit. So I just stole some of the guts of validates each, to get the magic reference to the record object. As a bonus, then my validation conforms to other validation conventions, like accepting :if => in an options hash.
  def self.validates_your_funky_non_validates_each_using_way(*attrs)
    options = attrs.last.is_a?(Hash) ? attrs.pop.symbolize_keys : {}
    attrs   = attrs.flatten
    # Declare the validation.
    send(validation_method(options[:on] || :save)) do |record|
      return if options[:if] && !evaluate_condition(options[:if], record)
       #put real code using attrs and record here
      record.errors.add :base, "monkey", if record.primate?
    end
  end