Your friends at Viget present Extend, a Code & Technology Blog

Named Scope Caching

When working on high-traffic Rails sites, it often becomes necessary to find ways to improve performance with caching. One place we’ve found this is most convenient and easy-to-do is by caching an ActiveRecord result set for models that change rarely or not at all. An easy example of this is a Category model.

Often times, you have a categorization hierarchy that will never or rarely change over the life of an application. Ideally you would fetch the results once from the database and never have to again. So how do we go about caching this? First let’s look at our model and create a named_scope for it:

class Category < ActiveRecord::Base
  acts_as_tree
  named_scope :find_top_level, :conditions => 'categories.parent_id IS NULL',
                              :order      => 'categories.name'
end

Next, we need to create create a method that fetches the results for our new scope and caches it in a class variable. It should also only do caching if in production environment (alternatively or additionally, we could use the ActionController.perform_caching config value), as this can cause problems in tests.

def self.top_level
  unless ('production' == RAILS_ENV) && ActionController.perform_caching
    @@top_level_cache = self.find_top_level
  else
    @@top_level_cache ||= self.find_top_level
  end
end

Finally, we need to create a method to invalidate our cache when records are saved or deleted. Since we know this isn’t happening often (if at all), this should rarely be performed but is a good safeguard so we know our cache is current.

after_save :reset_cached_finder
after_destroy :reset_cached_finder

def reset_cached_finder
  @@top_level_cache = nil
end

This is something that we could easily see doing in a number of models for a number of finders. Since this involves a lot of similar code, it would be great if we could create some meta code that would allow us to define these caches with a simple one liner.

Maybe with syntax like cache_scopes :cached_method_name => :scope_name.
For example:

cache_scopes :top_level => :find_top_level

Well here’s the code that does that:

Suggestions for improvement are encouraged, which is easily done with GitHub’s new gist.

Enjoy and have fun caching!


Get More From Viget

Subscribe to get our monthly newsletter and occasional special announcements.