def contentThis is aimed to allow for lazily initialized attributes of a class. In this case, the
@content ||= []
end
@content
instance variable is initialized with []
when it's accessor method content
is called, unless it has already been initialized. The ||=
operator means "if the left hand variable is nil
, set it to the right hand value, otherwise just return the left hand variable's value". However, as Piers points out, there is a problem with certain values due to the way Ruby treats boolean values and
nil
. Here an example to illustrate:
a = falseWhat's the result of this? Since a was initialized in the first line, the second line should not have had any effect. However, executing that code reveals that
a ||= "Ruby"
a
now has the value "Ruby", instead of false
. The problem becomes clear by remembering the common way to write
nil
checks in Ruby:
if nameIn Ruby, a
puts name.capitalize
end
nil
is interpreted as boolean false
, so the code in the if
clause will only run if the name
is not nil
. While this is usually not a problem, in the lazily initialized attributes code, it's a problem if a legal value for attribute is either
nil
or false
. In that case, an access would reset the variable to it's default value. This is certainly a corner case, however it's the kind of issue that can cause long debugging sessions, trying to figure out why some attributes are occasionally reset while others aren't.
Piers offers a more explicit version of the code:
def contentThis only initializes the variable if the variable hasn't been defined yet.
unless instance_variable_defined? :@content
@content = []
end
return @content
end
This little example could be blamed on Ruby and some of it's language features - but it's widely known which type of workers blame their tools instead of themselves. While the conciseness of Ruby is very helpful, there are cases where more explicit expression of intent is safer. In this case, the
||=
wasn't the right solution and instead the initialization code is supposed to check if the variable had been defined yet. Have you been bitten by issues such as this one? Are there Ruby features you like to avoid to prevent subtle errors?