Careful with '||=' - it's not 'memoize'

Posted . Visible to the public.

When you do something like this in your code:

def var_value
  @var ||= some_expensive_calculation
end

Be aware that it will run some_expensive_calculation every time you call var_value if some_expensive_calculation returns nil.

This illustrates the problem:

def some_expensive_calculation
  puts "i am off shopping bits!"
  @some_expensive_calculations_result
end

When you set @some_expensive_calculations_result to nil, ||= runs some_expensive_calculation every time.

>   var_value
    i am off shopping bits!
    => nil 
>   var_value
    i am off shopping bits!
    => nil 
>   var_value
    i am off shopping bits!
    => nil

This only changes, when @some_expensive_calculations_result is something other than nil:

> @some_expensive_calculations_result = 42
    => 42 
>   var_value
    i am off shopping bits!
    => 42
>   var_value
    => 42 
>   var_value
    => 42 

So when you are in performance trouble if you do something like this:

def show
  @resource ||= User.find(resource_params(:id))
end

It will only cache only if a valid @resource is found. If the result is nil, the database is asked again and again.

Why?

'||=' is a shorthand vor "nil or undefined", so if you want to cache something that can return nil, you can not use it. You will have to check for 'defined?' only, ignoring 'nil?'

something like this would be correct:

def cache_or_resolve(instance_variable_name, resolver)
  if !instance_variable_defined? "@#{instance_variable_name}"
     instance_variable_set("@#{instance_variable_name}", send(resolver))
  end
  instance_variable_get("@#{instance_variable_name}")
end

def var_value
  cache_or_resolve :var, :some_expensive_calculation
end

This is what "memoize" in earlier rails versions did. There is a gem Show archive.org snapshot that reintroduces this behavior, but this is the essential implementation.

Remember

In the above example, some_expensive_calculation is never called again. This might be what you want, but if you are looking for something in the database that might return a valid result later on, you need to invalidate the cache manually:

def var_value_refreshed
  remove_instance_variable "@var"
  var_value
end

For the "propper" solution, see the gem mentioned. This only outlines the gun, the foot and the trigger.

Last edit
License
Source code in this card is licensed under the MIT License.
Posted by Lexy to makandra dev (2014-01-05 17:53)