Reputation: 8516
Obviously ||=
won't work
def x?
@x_query ||= expensive_way_to_calculate_x
end
because if it turns out to be false
or nil
, then expensive_way_to_calculate_x
will get run over and over.
Currently the best way I know is to put the value into an Array
:
def x?
return @x_query.first if @x_query.is_a?(Array)
@x_query = [expensive_way_to_calculate_x]
@x_query.first
end
Is there a more conventional or efficient way of doing this?
UPDATE I realized that I wanted to memoize nil
in addition to false
- this goes all the way back to https://rails.lighthouseapp.com/projects/8994/tickets/1830-railscachefetch-does-not-work-with-false-boolean-as-cached-value - my apologies to Andrew Marshall who gave an otherwise completely correct answer.
Upvotes: 22
Views: 7585
Reputation: 96954
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.
Upvotes: 32
Reputation: 239302
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"
Upvotes: 32