Does ruby stop evaluating if statements when first condition is false?
Ruby, and most other programming languages use short circuiting boolean expressions. Meaning any expression of the form false && puts("hi")
will not run the right side of the expression puts("hi")
. This goes for if
conditions as well, anything with &&
really.
This is specially important to know because you always want to put faster or cheaper expressions/functions on the left side and more expensive expressions on the right side of a &&
operator.
Consider this
puts "hi" if expensive_method() && some_value
In the above example expensive_method
will always run. But what if some_value
is sometimes false? This would be more efficient:
puts "hi" if some_value && expensive_method()
Taking advantage of the possibility that some_value
might sometimes be false, we spare ourselves from having to evaluate expensive_method
in those cases.
In short, take advantage of boolean expression short circuiting.
https://en.wikipedia.org/wiki/Short-circuit_evaluation
For the exception to occur in the first line:
if !song.nil? && song.ready && !song.has_been_downloaded_by(event.author)
when song.ready
is executed, song
must equal nil
, but to reach song.ready
, !song.nil?
must be true, meaning song
is not nil
, a contradiction. Therefore, we conclude that song
must be nil
, so the first elsif
is executed:
elsif !song.ready
which is equivalent to
elsif !(nil.ready)
raising the exception.
More generally, error messages contain valuable information and deserve careful study. Yours would also identify the line where the exception occurred. The error message tells you that song
is nil
at that point. It therefore must have been nil
in the first statement, so the first statement would have evaluated nil
.
Consider putting your clause in a method and rewriting it as follows.
def process_song(song)
return nil, "song is nil" if song.nil?
return false, "The song is not ready yet. Try again once it is." unless song.ready
return false, "Yo, check your private messages, I've already sent you the song." \
if song.has_been_downloaded_by(event.author)
song.send_to_user(event.author)
true
end
to be called as follows.
outcome, msg = process_song(song)
Then, perhaps something like the following.
case outcome
when true
...
when false
puts msg
...
when nil
<raise exception with message that song is nil>
else
<raise exception with message>
end
msg
is nil
when outcome
is true
. The first three lines of the method are called "guard clauses".