Short Version: The easiest way to invalidate a cached view that depends on a model whenever the model updates is to use the model’s updated_at time in the cache key. Rails does this automatically if you pass a model object into the cache() method for fragment caching. The cache never expires, but a new update will result in a cache miss and regeneration. Memcached will get rid of the old records for you when it runs low on memory.

Rant Edition: So, I was working out how to do cache invalidation for models, and I find the Rails 2.3 conceptual model for invalidation flawed. You create sweepers, which are ostensibly observers that get called whenever a model object updates. Except they really don’t – you have to set them up with a controller object, and when the model callbacks fire, they call the cache invalidation method from that controller. The way you set them up is via the cache_sweeper method on… the controller. So really, sweepers get tied to one controller, since calling it from another controller would result in the cache invalidation methods being called on the wrong controller.

For example, let’s say we have a Dashboard controller which shows the most recent blog posts on a community site. Since it depends on the Post model, ideally you’d want to invalidate the Dashboard cache on the Post after_save callback. So you create a sweeper to listen on the Post object and invalidate the index cache on after_save. The problem is, the sweeper only gets loaded on the DashboardController; all the changes to Post happen in the PostsController. So maybe you can set up the sweeper to instantiate a DashboardController to invalidate the cache there, and then call the sweeper on the PostsController. But now wait, what about the BlogController? If you delete a blog, you get rid of all of its posts, so you have to put the sweeper there, too…

Essentially, you end up in the position of asking which controller actions will affect a model; and that’s not always obvious. It’s an ill-posed question to boot: the answer is “all of them.” You want the sweeper to fire every time the model is updated, no matter the controller. The real question you should be asking is, “What caches do I need to invalidate when this model object updates?”

I looked around a bit, but I didn’t see any clean-looking Railsy things that operate on this question. Maybe I missed something?

The workaround for the time being is as above; using the updated_at time as a cache key. Major thanks to RailsLab for providing a relatively clean answer to this problem.