# Transparent Caching Idiom

**URL:** <https://www.ruby-forum.com/t/transparent-caching-idiom/49982>\
**Category:** Ruby\
**Created:** [December 11, 2005, 5:25pm UTC](https://www.ruby-forum.com/t/transparent-caching-idiom/49982 "2005-12-11T17:25:31Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![beng](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/b3f665/32.png) [@beng](https://www.ruby-forum.com/u/beng)\
**Post date:** [December 11, 2005, 5:25pm UTC](https://www.ruby-forum.com/t/transparent-caching-idiom/49982/1 "2005-12-11T17:25:31Z")

</div>

I’ve made good use of this idiom recently, and wanted to share it.

Once this code is active, any method named with a “\_c” suffix is  
automatically cached. The method is assumed to follow a simple  
contract: It performs no side-effects / mutations, and it’s arguments  
properly implement “Object.hash”.

This is specifically useful for expensive but functional calculations.  
Enjoy!

–Ben (removed\_email\_address@domain.invalid)

# ``

class Class

```
alias old_new new
def new(*args, &block)
    Class.defc_wrap(old_new(*args, &block))
end

def Class.defc_wrap(obj)
    klass = obj.class
    defc_methods = obj.methods.select { |n| n =~ /_c$/ }
    defc_methods.each do |name|
        meth = obj.method(name)
        wrapped_name = Class.defc_next_name(klass)
        cache = klass.class_eval("@#{wrapped_name} = {}")
        wrapper = Proc.new do |*args_a|
            arg_hash = args_a.hash
            if cache.has_key?(arg_hash)
                cache[arg_hash]
            else
                cache[arg_hash] = meth.call(*args_a)
            end
        end
        klass.send(:alias_method, wrapped_name, name)
        klass.send(:define_method, name, wrapper)
    end
    obj
end

def Class.defc_next_name(obj)
    klass = obj.class
    i = 1
    i += 1 while obj.respond_to?("_defc_#{i}") ||

```

obj.instance\_variables.member?("@_defc_#{i}")  
“_defc_#{i}”  
end

end

---

<div class="post-metadata">

**Author:** ![beng](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/b3f665/32.png) [@beng](https://www.ruby-forum.com/u/beng)\
**Post date:** [December 11, 2005, 5:27pm UTC](https://www.ruby-forum.com/t/transparent-caching-idiom/49982/2 "2005-12-11T17:27:27Z")

</div>

Daniel S. wrote:

> > ```
> > def Class.defc_wrap(obj)
> > else
> > klass = obj.class
> > 
> > ```
> 
> Neat. Though it would be cool if the methods that were to be cached were  
> Daniel  
> I got this bookmarked, When i get time, i’ll have to compare to prior  
> thread on memozing  
> [http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/155159](http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/155159)  
> [http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/155170](http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/155170)

---

<div class="post-metadata">

**Author:** ![beng](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/b3f665/32.png) [@beng](https://www.ruby-forum.com/u/beng)\
**Post date:** [December 11, 2005, 5:27pm UTC](https://www.ruby-forum.com/t/transparent-caching-idiom/49982/3 "2005-12-11T17:27:27Z")

</div>

beng wrote:

> –Ben (removed\_email\_address@domain.invalid)  
> klass = obj.class  
> cache[arg\_hash] = meth.call(\*args\_a)  
> i = 1  
> i += 1 while obj.respond\_to?(“_defc_#{i}”) ||  
> obj.instance\_variables.member?("@_defc_#{i}")  
> “_defc_#{i}”  
> end
> 
> end

Neat. Though it would be cool if the methods that were to be cached were  
chosen through a class method rather than a method name suffix.

class Klass  
def foo; end  
def bar; end  
cached\_method :foo, :bar  
end

Cheers,  
Daniel

---

<div class="post-metadata">

**Author:** ![beng](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/b3f665/32.png) [@beng](https://www.ruby-forum.com/u/beng)\
**Post date:** [December 11, 2005, 5:27pm UTC](https://www.ruby-forum.com/t/transparent-caching-idiom/49982/4 "2005-12-11T17:27:28Z")

</div>

Gene T. wrote:

> > > calculations. Enjoy!
> > > 
> > > ```
> > > cache[arg_hash]
> > > def Class.defc_next_name(obj)
> > > 
> > > ```
> > 
> > Cheers,  
> > Daniel  
> > I got this bookmarked, When i get time, i’ll have to compare to prior  
> > thread on memozing  
> > [http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/155159](http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/155159)  
> > [http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/155170](http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/155170)

See also:  
[http://raa.ruby-lang.org/project/memoize/](http://raa.ruby-lang.org/project/memoize/)

Kind regards

```
robert

```

---

<div class="post-metadata">

**Author:** ![beng](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/b3f665/32.png) [@beng](https://www.ruby-forum.com/u/beng)\
**Post date:** [December 11, 2005, 5:27pm UTC](https://www.ruby-forum.com/t/transparent-caching-idiom/49982/5 "2005-12-11T17:27:28Z")

</div>

beng wrote:

> –Ben (removed\_email\_address@domain.invalid)  
> klass = obj.class  
> cache[arg\_hash] = meth.call(\*args\_a)  
> i = 1  
> i += 1 while obj.respond\_to?(“_defc_#{i}”) ||  
> obj.instance\_variables.member?("@_defc_#{i}")  
> “_defc_#{i}”  
> end
> 
> end

This is a touched-up version of your code.

class Class  
alias\_method : **new** , :new

def cached\_method(\*methods)  
@cached\_methods ||= []  
@cached\_methods += methods  
@cached\_methods.uniq!  
end

def new(\*args, &block)  
obj = **new** (\*args, &block)  
klass = obj.class  
@cached\_methods ||= []  
@cached\_methods.each do |name|  
meth = obj.method(name)  
i = 1  
while klass.instance\_variables.member?("@_defc_#{i}")  
i += 1  
end  
wrapped\_name = “_defc_#{i}”  
cache = klass.class\_eval("@#{wrapped\_name} = {}")  
wrapper = Proc.new do |\*args\_a|  
arg\_hash = args\_a.hash  
if cache.has\_key?(arg\_hash)  
cache[arg\_hash]  
else  
cache[arg\_hash] = meth.call(\*args\_a)  
end  
end  
klass.send(:alias\_method, wrapped\_name, name)  
klass.send(:define\_method, name, wrapper)  
end

```
 return obj

```

end  
end

class Klass  
def foo; end  
def bar; end  
cached\_method :foo, :bar  
end
