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.
nil and false are the only falsy values.
Everything else is considered truthy (source)
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 if object_a is a falsy value (either nil or false)object_b if both object_a and object_b are truthy values (not nil and not false)object_b if object_a is truthy and object_b is falsyYou can check that by running the examples I added in this Github gist: https://gist.github.com/lucianghinda/2677e6549c9e5927bd88d9c646524564
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 if object_a is truthy (not nil and not false)object_b if object_a is falsy (either nil or false)||= workComing 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:
loaded_key if loaded_key is not nil and not falseKey.find(params[:key]) if loaded_key is nil or false.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 is nilnil || @key = Key.find_by_key... will return @key = Key.find_by_key... as the first argument is falsy@key will have the value returned by Key.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])
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`
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"