Model Cache instead of Controller Cache


#1

Hi, I’ve made a model cache for rails, to use when you want to cache
model instance methods, rather than controller calls. Does anything
similar exist already? Any comments on the implementation?

Since it serializes cached data, it might be good to add the following
to environment.rb to get YAML-support for the rails class autoloading
mechanism.

– add to environment.rb

YAML support for class autoloading

YAML.add_domain_type(“ActiveRecord,2007”, “”) do |type, val|
klass = type.split(’:’).last.constantize
YAML.object_maker(klass, val)
end

class ActiveRecord::Base
def to_yaml_type
“!ActiveRecord,2007/#{self.class}”
end
def to_yaml_properties # only store the attributes
[’@attributes’]
end
end

– model_cache_helper.rb

ModelCache to use on an ActiveRecord class to cache method calls.

Example:

Class X < ActiveRecord::Base

def heavy_calculation(a, b)

“result #{self.x} #{a} #{b}”

end

include ModelCacheHelper

cache_method :heavy_calculation

end

The created method X.heavy_calculation(id, arg1, arg2) will then

check if the

result is cached, otherwise it will do

X.find(1).heavy_calculation(arg1, arg2)

and cache the result. Calling heavy_calculation on an instance of X

will also

cache the result.

The instance method expire_cache, will expire the cache for that

object. It

can be used with an optional argument, expire_cache(method), to only

expire

the cache for a certain method.

Non-string result will be serialized with YAML and automatically

deserialized

when loaded.

module ModelCacheHelper
def self.included(klass)
klass.extend ClassMethods
end

module ClassMethods
def check_cache(url, args)
c = ActionController::Base.new
return yield unless c.perform_caching

  url += args.map do |a|
    [String,Fixnum,Symbol,TrueClass,FalseClass].member?(a.class) ?

a.to_s : nil
end.join("-")

  unless f = c.read_fragment(url)
    res = yield
    c.write_fragment(url, res.is_a?(String) ? res : res.to_yaml)
    res
  else
    (f[0..3] == "--- ") ? YAML.load(f) : f
  end
end

def cache_method(method)
  controller = self.class_name.underscore.pluralize
  self.class_eval %Q{
    def self.#{method}(id, *args)
      check_cache("#{controller}/\#{id}-#{method}-", args) do
        self.find(id).#{method}_without_cache(*args)
      end
    end

    def #{method}_with_cache(*args)
      self.class.check_cache("#{controller}/\#{id}-#{method}-",

args) do
#{method}_without_cache(*args)
end
end
alias_method_chain :#{method}, :cache
}
end
end

def expire_cache(method = false)
c = ActionController::Base.new
return unless c.perform_caching
controller = self.class.class_name.underscore.pluralize
puts “expire #{controller} - id #{self.id}”
unless method
c.expire_fragment %r{#{controller}/#{self.id}-.}
else
c.expire_fragment %r{#{controller}/#{self.id}-#{method}-.
}
end
end
end


#2

Tobias Numiranta wrote:

Hi Tobias.

Do you have benchmarks for this that show a significant performance
improvement over re-calling the DB?

I believe that EDGE Rails (and maybe the current version now) have model
caching, simply storing DB calls in case the call is made again within
the same transaction. I believe however that you are keeping your cache
for multiple transactions.


#3

Hi. Yes, I get nice performance improvements since some of the model
methods takes a few seconds with a disabled cache. Seems like a nice
feature when you have database-intensive method calls.

Yes, EDGE seems to have some model caching, but only for a code-block.
Instead, these results are cached until you explicitly call
expire_cache, just like the cache in a controller.

On May 8, 2:26 pm, Douglas S. removed_email_address@domain.invalid


#4

Tobias,

A tiny detail but if you add

ActiveRecord::Base.send(:include, ModelCacheHelper)

in environment.rb, you’ll no longer have to explicitely include it,
and you’d just have to write:

cache_method :heavy_calculation

Alain R.

blog.ravet.com