#one?
Video transcript & code
In the last episode, we used Ruby's exclusive-or operator to verify that exactly one of a pair of options was supplied.
def replace_var(text, var_name, value=nil)
unless block_given? ^ value
raise ArgumentError,
"Either value or block must be given, but not both"
end
text.gsub!(/\{#{var_name}\}/) { value || yield }
end
Some of you probably watched that episode and said "that's great Avdi, but what if I have more than two exclusive options?" You might have even experimented with stringing more than one XOR operator together, in which case you probably discovered that this doesn't work for asserting that only one operand is truthy.
true ^ true ^ false ^ false ^ true # => true
So what do we do when we want to check that just one of an arbitrary set of values is truthy? For that case, Ruby gives us the #one?
predicate method.
It returns false if there are no truthy elements in a collection:
[false, nil, nil].one? # => false
…it also returns false if more than one member is truthy:
[42, nil, 'banana'].one? # => false
But if exactly one element is truthy, it returns true:
[42, nil, false].one? # => true
Of course, it's also true if there's just one element in the collection, and that element is truthy:
["just me"] # => ["just me"]
And it is always false for empty collections:
[].one? # => false
This makes it a succinct way to verify that a search returned one and only one result, so long as nil
and false
are not valid search results.
"waldo is at the waldorf".scan(/waldo/).one? # => false
#one? isn't just for counting truthy values. We can pass a block to it and have it look for any property of the collection elements.
[42, 85, 34, 6, 10].one?{|e| e.odd?} # => true
We can combine this with symbol-to-proc for some very concise code:
[42, 85, 34, 6, 10].one?(&:odd?) # => true
#one? is defined on
Enumerable
, so it's available on more than just arrays.
require 'set'
Set.new(["just me"]).one? # => true
And with that we come to the end of our introduction to the #one?
predicate. Happy hacking!
Responses