Ruby: What does && and || return?

Senior Product Engineer, working in Ruby and Rails. Passionate about idea generation, creativity, and programming.
I curate the Short Ruby Newsletter.
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_aifobject_ais a falsy value (eithernilorfalse)object_bif both object_a and object_b are truthy values (notniland notfalse)object_bifobject_ais truthy andobject_bis falsy
You can check that by running the examples I added in this Github gist: https://gist.github.com/lucianghinda/2677e6549c9e5927bd88d9c646524564
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_aifobject_ais truthy (notniland notfalse)object_bifobject_ais falsy (eithernilorfalse)
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_keyifloaded_keyis notniland notfalse - will return
Key.find(params[:key])ifloaded_keyisnilorfalse.
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:
@keyis undefined => thus its value isnilnil || @key = Key.find_by_key...will return@key = Key.find_by_key...as the first argument is falsy- =>
@keywill 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"




