Obviously ||= won\'t work
def x?
@x_query ||= expensive_way_to_calculate_x
end
because if it turns out to be false
To account for nil, use defined? to see if the variable has been defined:
def x?
return @x_query if defined? @x_query
@x_query = expensive_way_to_calculate_x
end
defined? will return nil if the variable hasn't been defined, or the string "instance_variable" otherwise:
irb(main):001:0> defined? @x
=> nil
irb(main):002:0> @x = 3
=> 3
irb(main):003:0> defined? @x
=> "instance-variable"
Explicitly check if the value of @x_query is nil instead:
def x?
@x_query = expensive_way_to_calculate_x if @x_query.nil?
@x_query
end
Note that if this wasn't an instance variable, you would have to check if it was defined also/instead, since all instance variables default to nil.
Given your update that @x_query's memoized value can be nil, you can use defined? instead to get around the fact that all instance variables default to nil:
def x?
defined?(@x_query) or @x_query = expensive_way_to_calculate_x
@x_query
end
Note that doing something like a = 42 unless defined?(a) won't work as expected since once the parser hits a =, a is defined before it reaches the conditional. However, this isn't true with instance variables since they default to nil the parser doesn't define them when it hits =. Regardless, I think it's a good idiom to use or or unless's long block form instead of a one-line unless with defined? to keep it consistent.