cached_model and single table inheritance
January 4, 2007
Casey Muller

We're not actually using cached_model for Jamglue, but since I took the time to figure it out, I thought I'd post on how to make the Robot Coop's cached_model work with Single Table Inheritance.

I like the idea of cached_model- make your models inherit from CachedModel instead of ActiveRecord::Base, and get automatic transparent speed-ups.

But there are two problems that arise if you also use single table inheritance though. To illustrate them, I'll go with the old example of a Shape class, with Circle and Square children.

So we go from class Shape < ActiveRecord::Base, with Circle < Shape and Square < Shape, to having Shape < CachedModel and the other two unchanged.

The first problem is that while Circle.find(3) and Square.find(5) keep working, the more general Shape.find(3) no longer returns anything. A glance at the log shows that the SQL query now includes the requirement that type = 'Shape', which of course never happens (type is always Circle or Square).

This happens because anytime a class isn't an immediate subclass of ActiveRecord::Base, single table inheritance adds the type condition. The fastest way to fix this is to override descends_from_active_record? on the parent class, Shape:

  def self.descends_from_active_record?
    # hide the existance of CachedModel so that find won't add a type condition
    return name == "Shape"
  end

The second problem is with the caching. CachedModel uses keys of the form Class:id, but if you watch memcached's logs, you'll see that both Circle:3 and Shape:3 end up being set and get, which is inefficient and also means you're probably only expiring one whenever the data changes.

To fix this, we override cache_key_local, again in the parent class, Shape. We force it to always use Shape in the key, unifying the keys in the cache:

  def cache_key_local
    # always use Shape, never Circle or Square
    return "Shape:#{id}"
  end

Both of these fixes are kind of hacky, but they may help somebody else trying to quickly combine cached_model with single table inheritance. Also, I thought anybody writing their own model caching system might be interested to see the shortcomings with the current implementation of cached_model for future reference.

previous entry:

A night of candlelit pounce