简体   繁体   中英

Rails Memoization of Helper method

I have a helper method that does expensive calculations and returns a Hash , and this Hash is constant during my entire application lifespan (meaning it can only change after a re-deploy) and it also doesn't take any arguments.

For performance, I wish I could 'cache' the resulting Hash .

I don't want to use Rails cache for this, since I want to avoid the extra trip to memcached and I don't want the overhead of de-serializing the string into a hash.

My first idea was to assign the resulting hash to a Constant and calling .freeze on it. But the helper is an instance method, the constant lives on the class, and I had to do this ultra hacky solution:

module FooHelper

def expensive_calculation_method
  resulting_hash
end

EXPENSIVE_CALCULATION_CONSTANT = Class.new.extend(self).expensive_calculation_method.freeze

This is due to the helper method being an instance method, the helper being a Module (which leads to the fake Class extend so I can call the instance method) and I also must declare the constant AFTER the instance method (if I declare it right after module FooHelper , I get an undefined method 'expensive_calculation_method' .

The second idea was to use memoization, but at least for Rails Controllers memoization is the persistance of a variable over the lifecycle of a single request , so it's only valuable if you reuse a variable many times from within a single request , which is not my case, but at the same time Helpers are modules, not Classes to be instanciated, and by this point I don't know what to do.

How would I cache that Hash, or memoize it in a way that persists over requests?

If you want to cache the result of some painful operation at launch time:

module MyExpensiveOperation
  COMPUTED_RESULT = OtherModule.expensive_operation

  def self.cached
    COMPUTED_RESULT
  end
end

Just make sure that module's loaded somehow or it won't initiate. You can always force- require that module if necessary in environment.rb or as a config/initializer type file.

If you want to lazy load the basic principle is the same:

module MyExpensiveOperation
  def self.cached
    return @cached if (defined?(@cached))

    @cached = OtherModule.expensive_operation
  end
end

That will handle operations that, for whatever reason, return nil or false . It will run once, and once only, unless you have multiple threads triggering it at the same time. If that's the case there's ways of making your module concurrency aware with automatic locks.

Per your comments, this will only change at application boot, so placing it in an initializer would do the trick.

# config/initializers/expensive_thing.rb
$EXENSIVE_THING_GLOBAL = expensive_calculation

# or
EXPENSIVE_THING_CONSTANT = expensive_calculation

# or

Rails.application.config.expensive_thing = expensive_calcualatioin

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM