Reputation: 5962
I'm writing some code to evaluate the presence of a bunch of strings, and I want to ensure that only 1 is present. They're mutually exclusive.
class MyClass
include ActiveModel::Validations
attr_accessor :a, :b, :c
validate :mutex_values
def initialize(attr = {})
attr.each do |k, v|
send("#{k}=", v)
end
end
private
def mutex_values
# here, I want to do this:
# errors.add(:base, "specify only 1") unless a ^ b ^ c
# instead I do this
errors.add(:base, "specify only 1") unless a.present? ^ b.present? ^ c.present?
end
end
MyClass.new(:a => "A", :b => "B", :c => "C").valid?
=> false
Is there another way that doesn't require repetitive use of the .present?
? Monkey patch String to define an operator ^
? I'm just so used to being able to do if a
that it feels unnatural to need what is basically an explicit cast to boolean. I feel like this use case would 'just work' in Ruby.
Upvotes: 0
Views: 429
Reputation: 44080
If you want to check whether exactly one of methods a
, b
, and c
returns non-null value, you can use:
[a, b, c].compact.count == 1
or even, thanks to numbers,
[a, b, c].one?
Upvotes: 1
Reputation: 34072
Enumerable actually defines a one?
method which does exactly what you need.
# So either...
[a, b, c].one?(&:present?)
# or
[a, b, c].one?(&:presence)
# ... would do the trick in this case.
Unfortunately if you want two?
or etcera, you're out of luck.
Upvotes: 2
Reputation: 434685
You could do something like this with count
:
errors.add(:base 'specify exactly 1') unless [a, b, c].count(&:present?) == 1
If you want at most one instead of exactly one, then:
errors.add(:base 'specify at most 1') unless [a, b, c].count(&:present?) > 1
For example:
> ['',nil,'6',''].count(&:present?)
=> 1
> ['',nil,'6',11].count(&:present?)
=> 2
> ['',nil,''].count(&:present?)
=> 0
Upvotes: 2