After studying DHH's and other blog articles about key-based cache expiration and Russian Doll Caching, I am still unsure how to handle one relation type. To be specific, a has_many relationship.
I will share the results of my research on a sample app. It is a little bit of story telling, so hang on. Let's say we have the following ActiveRecord models. All we care about is a proper change of the model's cache_key, right?
class Article < ActiveRecord::Base
  attr_accessible :author_id, :body, :title
  has_many :comments
  belongs_to :author
end
class Comment < ActiveRecord::Base
  attr_accessible :article_id, :author_id, :body
  belongs_to :author
  belongs_to :article, touch: true
end
class Author < ActiveRecord::Base
 attr_accessible :name
  has_many :articles
  has_many :comments
end
We already have one article, with one comment. Both by a different author. The goal is to have a change in the cache_key for the article in the following cases:
- Article's body or title changes
 - Its comment's body changes
 - Article's author's name changes
 - Article's comment's author's name changes
 
So by default, we are good for case 1 and 2.
1.9.3-p194 :034 > article.cache_key
 => "articles/1-20130412185804"
1.9.3-p194 :035 > article.comments.first.update_attribute('body', 'First Post!')
1.9.3-p194 :038 > article.cache_key
 => "articles/1-20130412185913"
But not for case 3.
1.9.3-p194 :040 > article.author.update_attribute('name', 'Adam A.')
1.9.3-p194 :041 > article.cache_key
 => "articles/1-20130412185913"
Let's define a composite cache_key method for Article.
class Article < ActiveRecord::Base
  attr_accessible :author_id, :body, :title
  has_many :comments
  belongs_to :author
  def cache_key
    [super, author.cache_key].join('/')
  end
end
1.9.3-p194 :007 > article.cache_key
 => "articles/1-20130412185913/authors/1-20130412190438"
1.9.3-p194 :008 > article.author.update_attribute('name', 'Adam B.')
1.9.3-p194 :009 > article.cache_key
 => "articles/1-20130412185913/authors/1-20130412190849"
Win! But of course this does not work for case 4.
1.9.3-p194 :012 > article.comments.first.author.update_attribute('name', 'Bernard A.')
1.9.3-p194 :013 > article.cache_key
 => "articles/1-20130412185913/authors/1-20130412190849"
So what options are left? We could do something with the has_many association on Author, but has_many does not take the {touch: true} option, and probably for a reason. I guess it could be implemented somewhat along the following lines.
class Author < ActiveRecord::Base
  attr_accessible :name
  has_many :articles
  has_many :comments
  before_save do
    articles.each { |record| record.touch }
    comments.each { |record| record.touch }
  end
end
article.comments.first.author.update_attribute('name', 'Bernard B.')
article.cache_key
  => "articles/1-20130412192036"
While this does work. It has a huge performance impact, by loading, instantiating and updating every article and comment by that other, one by one. I don't believe it is a proper solution, but what is?
Sure the 37signals use case / example might be different: project -> todolist -> todo. But I imagine a single todo item also belonging to a user.
How would one solve this caching problem?