Named Scope Caching

Brian Landau, Former Developer

Article Category: #Code

Posted on

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 => '' 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!

Related Articles