DZone Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world
Object.memoize
The following is inspired by the article "<a href="http://blog.grayproductions.net/articles/caching_and_memoization">Caching and Memoization</a>" by James Edward Gray II.
# = Memoization for objects
#
# This module will extend +Object+ with the +memoize+ method. This method
# provides memoization for instance methods, which means return values will
# be cached and subsequent calls will return the cached value of the first
# call.
#
# Caching is done based on instance, method and arguments to the method. All
# data is kept in a single +Hash+ store which allows flushing all cached
# results at ones using the +flush_memos+ method.
#
# == Example
# class Person < Struct.new(:email)
# def finger
# `finger #{email}`
# end
# memoize :finger
# end
#
# bob = Person.new('bob@test.net')
# bob.finger # finger command executed
# bob.finger # cached value returned
# Memoizable.flush_memos
# bob.finger # finger command executed
#
# == See also
# http://blog.grayproductions.net/articles/caching_and_memoization
#
# == Author
# R.W. van 't Veer, 2008-04-01, Amsterdam
module Memoizable
# Store for cached values.
CACHE = Hash.new{|h,k| h[k] = Hash.new{|h,k| h[k] = {}}} # 3 level hash; CACHE[:foo][:bar][:yelp]
# Memoize the given method(s).
def memoize(*names)
names.each do |name|
unmemoized = "__unmemoized_#{name}"
class_eval %Q{
alias :#{unmemoized} :#{name}
private :#{unmemoized}
def #{name}(*args)
cache = CACHE[self][#{name.inspect}]
cache.has_key?(args) ? cache[args] : (cache[args] = send(:#{unmemoized}, *args))
end
}
end
end
# Flush cached return values.
def flush_memos
CACHE.clear
end
module_function :flush_memos
end
class Object # :nodoc:
extend Memoizable
end
if $0 == __FILE__
require 'test/unit'
class MemoizeTest < Test::Unit::TestCase # :nodoc:
def setup
@obj = TestObject.new
end
def teardown
Memoizable.flush_memos
end
def test_memoize_value_should_stick_until_cache_flushed
@obj.value = 'a'
assert_equal 'a', @obj.value
@obj.value = 'b'
assert_equal 'a', @obj.value
Memoizable.flush_memos
assert_equal 'b', @obj.value
end
def test_flush_should_clear_all_cached_objects
@obj.value = 'yelp'
@obj.value
assert_not_equal 0, Memoizable::CACHE.size
Memoizable.flush_memos
assert_equal 0, Memoizable::CACHE.size
end
def test_memoize_should_keep_separate_cache_per_instance
other = TestObject.new
@obj.value, other.value = 'a', 'b'
assert_equal 'a', @obj.value
assert_equal 'b', other.value
end
def test_memoize_should_keep_separate_cache_per_method
@obj.value, @obj.other = 'a', 'b'
assert_equal 'a', @obj.value
assert_equal 'b', @obj.other
end
def test_memoize_should_include_arguments_in_cache_key
@obj.with_arguments = 'a'
assert_equal 'a', @obj.with_arguments(:this)
@obj.with_arguments = 'b'
assert_equal 'a', @obj.with_arguments(:this)
assert_equal 'b', @obj.with_arguments(:that)
end
class TestObject # :nodoc:
attr_accessor :value, :other
memoize :value, :other
attr_writer :with_arguments
def with_arguments(*args); @with_arguments; end
memoize :with_arguments
attr_writer :question
def question?; @question; end
memoize :question?
attr_writer :exclamation
def exclamation!; @exclamation; end
memoize :exclamation!
end
end
end





