I wrote a simple Cacheable module that makes it simple to cache aggregate fields in a parent model. The module requires that the parent object implement the cacheable method and a calc_ method for each field that requires caching at the parent level.
module Cacheable
def cache!(fields, *objects)
objects.each do |object|
if object.cacheable?
calc(fields, objects)
save!(objects)
end
end
end
def calc(fields, objects)
fields.each { |field| objects.each(&:"calc_#{field}") }
end
def save!(objects)
objects.each(&:save!)
end
end
I would like to add callbacks to the ActiveRecord model into which this module is included. This method would require that the model implement a hash of parent models and field names that require caching.
def cachebacks(klass, parents)
[:after_save, :after_destroy].each do |callback|
self.send(callback, proc { cache!(CACHEABLE[klass], self.send(parents)) })
end
end
This approach works great if I manually add both callbacks using such as:
after_save proc { cache!(CACHEABLE[Quote], *quotes.all) }
after_destroy proc { cache!(CACHEABLE[Quote], *quotes.all) }
But, I’m receiving the following error when I try to use the cachebacks method to add these to callbacks.
cachebacks(Quote, "*quotes.all")
NoMethodError: undefined method `cachebacks' for #<Class:0x007fe7be3f2ae8>
How do I add these callbacks to the class dynamically?
This looks like a good case for
ActiveSupport::Concern. You can tweak yourcachebacksmethod slightly to add it as a class method on the including class:To use it:
The block you pass to
cachebackswill be executed in the context of the class that’s calling it. In this example,{ all }is equivalent to callingExample.alland passing the results into yourcache!method.To answer your question in the comments,
Concernencapsulates a common pattern and establishes a convention in Rails. The syntax is slightly more elegant:It also takes advantage of another convention to automatically and correctly include class and instance methods. If you namespace those methods in modules named
ClassMethodsandInstanceMethods(although as you’ve seen,InstanceMethodsis optional), then you’re done.Last of all, it handles module dependencies. The documentation gives a good example of this, but in essence, it prevents the including class from having to explicitly include dependent modules in addition to the module it’s actually interested in.