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?
2
Answers
If you want to cache the result of some painful operation at launch time:
Just make sure that module’s loaded somehow or it won’t initiate. You can always force-
require
that module if necessary inenvironment.rb
or as aconfig/initializer
type file.If you want to lazy load the basic principle is the same:
That will handle operations that, for whatever reason, return
nil
orfalse
. 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.