How do these things work?
Have you ever wondered how the following code works? Specifically, how come the key will have another value than true or false.
key = loaded_key || Key.find(params[:key])
or maybe how does the simple memorization pattern work:
def key
@key ||= Key.find(params[:key])
end
The answer is in the idea that ||
and &&
do not return the response asTrueClass
or FalseClass
objects (or, more precisely, do not return the global value true
or false
) but it returns one operand or the other.
What is false and what is true in Ruby
nil
and false
are the only falsy
values.
Everything else is considered truthy
(source)
What does && return
x && y
# returns x if x is false
# returns y if x is true
A simple truth table might look like this:
# +────────+─────────────+─────────+
# | X | Y | Return |
# +────────+─────────────+─────────+
# | false | true/false | X |
# | true | true | Y |
# | true | false | Y |
# +────────+─────────────+─────────+
But as in Ruby, we have the concept of falsy
values, let's expand this table:
# +──────────+─────────+─────────+─────────+
# | X | Y | Result | Return |
# +──────────+─────────+─────────+─────────+
# | nil | Truthy | nil | X |
# | nil | Falsy | nil | X |
# | false | Truthy | false | X |
# | false | nil | false | X |
# | true | Falsy | Falsy | Y |
# | Truthy | Truthy | Truthy | Y |
# +──────────+─────────+─────────+─────────+
When evaluating object_a && object_b
Ruby will return:
object_a
ifobject_a
is a falsy value (eithernil
orfalse
)object_b
if both object_a and object_b are truthy values (notnil
and notfalse
)object_b
ifobject_a
is truthy andobject_b
is falsy
You can check that by running the examples I added in this Github gist: gist.github.com/lucianghinda/2677e6549c9e59..
What does || return
x || y
# returns x if x is true without evaluating y
# returns y if x is false
Let's try to put this in a kind of truth table:
# +───────+─────────────+─────────+
# | X | Y | X || Y |
# +───────+─────────────+─────────+
# | true | true/false | X |
# | false | true/false | Y |
# +───────+─────────────+─────────+
And then to expand it by taking into consideration the concept of truthy
and falsy
from Ruby:
# +────────+─────────+─────────+─────────+
# | X | Y | Result | Return |
# +────────+─────────+─────────+─────────+
# | nil | Truthy | Truthy | Y |
# | nil | Falsy | Falsy | Y |
# | false | Truthy | Truthy | Y |
# | false | Falsy | Falsy | Y |
# | Truthy | Falsy | Truthy | X |
# | Truthy | Truthy | Truthy | X |
# +────────+─────────+─────────+─────────+
We can then say that object_a || object_b
will return:
object_a
ifobject_a
is truthy (notnil
and notfalse
)object_b
ifobject_a
is falsy (eithernil
orfalse
)
Explaining how the ||=
work
Coming back to the question asked at the beginning, how does the following piece of code work:
key = loaded_key || Key.find(params[:key])
We can read it like this:
- will return
loaded_key
ifloaded_key
is notnil
and notfalse
- will return
Key.find(params[:key])
ifloaded_key
isnil
orfalse
.
What about memoization:
def key
@key ||= Key.find(params[:key])
end
First this code is actually a shorthand for:
def key
@key || @key = Key.find_by_key(params[:key])
end
There are two pieces of knowledge that we need to explain this along with how || works thoroughly:
"Undefined instance variable returns nil" (source)
"a ||= b approximatively translates to a || a = b and not a = a || b" (source)
What happens when key
method is called first:
@key
is undefined => thus its value isnil
nil || @key = Key.find_by_key...
will return@key = Key.find_by_key...
as the first argument is falsy- =>
@key
will have the value returned byKey.find_by_key(params[:key])
Now let's run this a second time. What happens then?
It depends on the previous result ofKey.find_by_key(params[:key])
.
If a Key was found, then the initial expression will be evaluated as Truthy || Object
and return the Truthy object that in this case is @key
:
@key || @key = Key.find_by_key(params[:key])
# when @key is not falsy is returning:
@key
If a Key was not found and the result of find_by_key
is nil
then the second run will be evaluates as Falsy || Object
and return the Object:
@key || @key = Key.find_by_key(params[:key])
# when @key is falsy is returning and executing the initialize of @key:
@key = Key.find_by_key(params[:key])
More examples:
Transforming true
in 1 and false
in 0
# This method transforms true to 1 and false to 0
def transform(x)
x && 1 || 0
end
(source for the idea to convert boolean to integers using short-circuit operators)
When x is true:
true && 1 || 0
=> (true && 1) || 0
=> 1 || 0
=> 1
When x is false:
false && 1 || 0
=> (false && 1) || 0
=> false || 0
=> 0`
Responding with one object or another if argument is true
def success(response)
puts "Success"
end
def error
puts "Error"
end
def render(response)
response && success(response) || error
end
render(nil) #=> "Error"
render(false) #=> "Error"
render(true) # => "Success"
render(1) #=> "Success"