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.

Leave a Reply

You must be logged in to post a comment.