Validating the presence of exactly one of a list of attributes
I read Eric’s blog on creating an ActiveRecord validation for an exactly-one-of predicate. I myself had just recently gone through a similar exercise and found a straightforward way of expressing it using the validate method:
validate :exactly_one_of?
def exactly_one_of?
attrs = [:url, :file, :blob]
returning (attrs.inject(false){|m, o| m^self[o]}) do |bool|
self.errors.add(:base, “Exactly one of #{attrs} must be set.”) unless bool
end
end
While this worked nicely for me, Eric went to the trouble of building a semantically clean validation method that mimics the options of the standard validations. I prefer using Ruby’s Exclusive OR operator combined with inject instead of the looping in Eric’s method, but I like the semantic style he defined. Combining his semantics with my logic yields something slick and DRY like this:
def self.validate_exactly_one_of(*attrs)
options = attrs.last.is_a?(Hash) ? attrs.pop : {}
configuration = { :message => “one of #{attrs.to_sentence :connector => ‘or’} must be set”, :on => :save }
configuration.update(options)send(validation_method(options[:on] || :save)) do |record|
returning (attrs.inject(false){|m, o| m^record[o]}) do |bool|
self.errors.add(:base, configuration[:message]) unless bool
end
end
end
I also fiddled the options logic because I think otherwise it would have been possible to have an attribute listed in the error message list that was in fact an option.