Can someone explain the reasoning being this? Just spent 30 mins trying to figure out why my boolean method returned nil
and found out that in Ruby:
2.2.1 :001 > nil && true
=> nil
2.2.1 :002 > nil && false
=> nil
Since nil
is a falsey value, I would have expected the output of nil && true
to be false. Also it seems to go against the idea that conditional operators should return a boolean value.
What is the rationale behind this?
It makes sense that the boolean operator is not commutative:
nil && false != false && nil
For others seeing this, my issue was that in rails I had a statement like:
def some_method?
object.attr && object.attr > something
end
But when object.attr
is nil, the function will be nil. Which is fine in most cases but when chaining boolean methods together, not so much. I just changed it to this instead:
def some_method?
object.attr.present? && object.attr > something
end
I could do the same thing in vanilla Ruby with:
def some_method?
!!object.attr && object.attr > something
end
The statement goes through the conditions in order, will stop when a falsy result is obtained and return the value of the last evaluation performed.
In contrary to &&
which stops at a falsy value, ||
will stop at a truthy value instead.
If first operand is falsy (nil
or false
), then, second operand of &&
will not be evaluated, as falsy && whatever
will be always falsy
Problem
You list two related problems:
- You are confused by a Boolean expression returning
nil
instead of false
.
- You are experiencing surprising behavior when chaining methods when one of the methods may evaluate to
nil
, false
, or some other object that doesn't respond to the next method in the chain.
Nil And False
In your examples, you are chaining methods that may sometimes be called on nil
. In Ruby, nil is a falsey value (e.g. it isn't "true"), but it is not actually false, either. Consider:
nil == false
#=> false
They aren't even descended from the same class.
nil.class
#=> NilClass
false.class
#=> FalseClass
It is often best to think of nil as a special value meaning "undefined" or "not applicable". It may also be useful to think of nil as similar to the special database value of null. Nil (like null) isn't true, false, or empty, and understanding this will help you avoid many confusing exceptions.
Short-Circuit Evaluation
When you evaluate nil && false
you're really evaluating two separate expressions:
(nil) && (false)
The expression nil
is not true, and therefore short-circuits and never evaluates the expression false
. As a result, the expression nil && false
will correctly return nil rather than false. This is a source of surprise for some people, but is expected behavior for experienced Rubyists.
Solutions
In addition to short-circuit expressions or post-expression conditionals such as:
# Send :to_s unless condition evaluates to false or nil.
object.attr.to_s if object.attr
you should consider the Rails Object#try method, or the Ruby 2.3.0 safe navigation operator.
Use Object#try or Safe Navigation Operator
Object#try
If you're using Rails, the Object#try method is a common way to avoid invoking methods on nil, or other objects that don't #respond_to? the chained method. For example:
# Invoke #to_s without raising an exception. May return nil.
object.attr.try(:to_s)
This is similar, but not exactly equivalent, to:
object.attr && object.attr.to_s
Ruby 2.3.0 introduces the safe navigation operator (&
) which brings similar functionality to Ruby's core.
The &
Safe Navigation Operator
Ruby 2.3.0 introduced a new safe navigation operator. This appears to be an actual operator, not a method. This operator lets you chain methods as long as the expression on the left is truthy. For example:
object.attr&.to_s
Because it chains methods, and because comparison operators like >
are really methods rather than parser tokens, you can now do things like:
1&.> 0
#=> true
1&.> 2
#=> false
Whether or not you find object.attr&. > something
more elegant, readable, or useful than other constructs is a matter of opinion, but it's certainly an option in many cases. YMMV.
It's good to have an understanding of variables/values "truthiness":
First, looking at Ruby's idea of true and false:
if true
'is true' # => "is true"
else
'is false' # =>
end
if false
'is true' # =>
else
'is false' # => "is false"
end
A shorter form of that if
/else
uses the ternary:
true ? true : false # => true
false ? true : false # => false
Continuing with that:
nil ? true : false # => false
0 ? true : false # => true
1 ? true : false # => true
Notice that nil
, when used as a conditional acts like false
, whereas 0
is true. (Perl treats false
and 0
as false values in tests which is a confusing point when moving to Ruby.) 1
is true, and basically, in Ruby, only nil
and false
are false and everything else is true.
!
performs a "not", reversing the truthiness of the value:
!true ? true : false # => false
!false ? true : false # => true
!nil ? true : false # => true
!0 ? true : false # => false
!1 ? true : false # => false
And !!
"nots" the value twice, which we use as a way to quickly convert something from a non-true/false value to a true/false:
!!true ? true : false # => true
!!false ? true : false # => false
!!nil ? true : false # => false
!!0 ? true : false # => true
!!1 ? true : false # => true
Knowing how these work in Ruby makes it easier to understand other people's code as you'll see !
and !!
often.
All that leads back to using &&
and ||
. Using ||
("or"), only one side has to be true
to return a true
result:
true || true # => true
false || false # => false
true || false # => true
false || true # => true
With &&
("and") both sides have to be true
to get a true
result:
true && true # => true
false && false # => false
true && false # => false
false && true # => false
Don't confuse ||
and or
, and &&
and and
. Both !!
and or
do similar things, and &&
and and
, however their tests will occur at different times in an expression. That's order-of-precedence and is very important to know but is a different question. You can find plenty of information about that with simple searches.