The Problem
I recently ran into this bug during a code review. In an attempt to avoid long chains of or
statements, code like the following was introduced:
if [
some_model.nil?,
some_model.some_attr,
# ...
].any?
return false
end
It is simple to understand the intent, however, errors like the following started to appear:
NoMethodError: undefined method `some_attr' for nil:NilClass
The Cause
At first glance, it appears that the Array#any?
method does not exit early on the first truthy value it encounters. However, what is actually happening is that the contents of the array are being evaluated before they are inserted into the array.
The Solution
The problem here is only a problem because we expected a previous if
condition to protect us from errors in a subsequent condition. There are many ways to avoid this.
One could use or
statements:
if some_model.nil? || some_model.some_attr # || ...
return false
end
One could use nil
-safe attribute accessors:
if [
some_model&.some_attr,
# ...
].any?
return false
end
In the end, I've come to prefer using or
statements. Using the #any?
statement makes it more likely that bugs may sneak in undetected if the writer of the code or the reviewer are familiar with this nuance or if tests aren't written to catch the condition. Keeping it simple by using or
statements is more clear and not subject to language-specific nuance.