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".

Tags:

Ruby