Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to fully reload an ActiveRecord to reset it's memoized values?

In an RSpec test, I create a record that has several memoizied values.

foo.reload works as expected for the object's attributes, but memoized ones are still present.

So far, it works by completely recreating the object: foo = Foo.find(123) but in my case, the logic for finding the record is actually more complex.

What's a good, DRY way to fully reload the record and erase all memoized values?

like image 947
martini-bonanza Avatar asked Feb 02 '15 15:02

martini-bonanza


3 Answers

You can do that by redefining reload. For example if you memoized with ||= you can do that:

class Foo < ActiveRecord::Base
  def bar
    @bar ||= rand
  end

  def reload
    @bar = nil
    super
  end
end

For ways to reset all instance variables see Is there a clean API for resetting instance variables on 'reload' in ActiveRecord?

If you use the memoist gem you can call flush_cache there.

like image 101
Michaël Witrant Avatar answered Nov 13 '22 01:11

Michaël Witrant


The good way is the one you already have: Completely recreating the object.

You cannot "reload" the object's memoized values in any easy "Rails" way, as memoizing attributes isn't a function of Rails or ActiveRecord. Neither knows anything about how you're memoizing methods.

like image 20
meagar Avatar answered Nov 13 '22 01:11

meagar


Michael's answer is sound, but be aware that if you use the more sophisticated pattern for also memoization nil and false values, that will NOT reset the memoization.

For instance, using the same example from the article for memoization nil and false values:

def main_address
  return @main_address if defined? @main_address
  @main_address = begin
    main_address = get_home_address_from_api
    main_address ||= get_work_address_from_api
    main_address ||= another_api_call.first
  end
end

This pattern works great because it also memoizes nil and false, but of course, to reset it, it wouldn't be enough to simply set the @main_address to nil, because it would be already be defined and return early.

In these cases, you should reset it like this:

def reset_main_address_memoization
  self.send(:remove_instance_variable, :@main_address) if instance_variable_defined?(:@main_address)
end
like image 3
sandre89 Avatar answered Nov 12 '22 23:11

sandre89