Thursday
Feb122009
A Note on Memoization in Rails
Thursday, February 12, 2009 at 2:51PM
If you've been following along at home, you know that memoization was added to Rails 2.2 as a shortcut for a common idiom:
[sourcecode language='ruby']
def site_term_ids
@site_term_ids || = cached_terms.split.map
{ |term| Term.find_by_query(term).id }
end
[/sourcecode]
Memoization makes it possible to perform an expensive operation once, and then cache the results for future calls to the same property. What you may have missed, though, is that this isn't baked into Active Record; it's a separate part of Active Support. So if you're getting mystery errors about memoization not working, be sure you're including the right extension:
[sourcecode language='ruby']
class Site < ActiveRecord::Base
extend ActiveSupport::Memoizable
def site_term_ids
cached_terms.split.map
{ |term| Term.find_by_query(term).id }
end
memoize :site_term_ids
end
[/sourcecode]
[sourcecode language='ruby']
def site_term_ids
@site_term_ids || = cached_terms.split.map
{ |term| Term.find_by_query(term).id }
end
[/sourcecode]
Memoization makes it possible to perform an expensive operation once, and then cache the results for future calls to the same property. What you may have missed, though, is that this isn't baked into Active Record; it's a separate part of Active Support. So if you're getting mystery errors about memoization not working, be sure you're including the right extension:
[sourcecode language='ruby']
class Site < ActiveRecord::Base
extend ActiveSupport::Memoizable
def site_term_ids
cached_terms.split.map
{ |term| Term.find_by_query(term).id }
end
memoize :site_term_ids
end
[/sourcecode]

Reader Comments (5)
Just a note, memoize doesn't play with with Model#destroy as both mess with Object#freeze
Hmm... this replaces a simple Ruby idiom with a module and a method of unknown complexity. I don't find it very compelling, I have to say...
I agree with Ashley, but I'd go as far as to say it's ridiculous. It's obfuscation, and to what end? I've been looking forward to Rails 3, but if this kind of code is still the order of the day then maybe I should start to worry.
@ Ashley and Graham
memoize doesn't just replace a Ruby idiom, it improves it. With caching, your expensive operation will run every time if the result is nil or false. Memoize will remember the value no matter what it is. Also caching will fail if you operation is based on any arguments, but memoize will remember the result for each set of arguments.
What I love about the new memoize functionality is that it is so well documented.
What I a supposed to do, walk through the test cases and try to divine the capabilities from them?
If it doesn't play nice with destroy, then there are substantial problems.
As for specifics, the example cited are simplified. If your function takes arguments, the idiom looks like:
def initialize
...
@cached_func_vals = {}
...
end
def func(*args)
return @cached_func_vals[args] if @cached_func_vals.has_key? args
... do computations ...
value = last computation
@cached_func_vals[args] = value
end