[Feature proposal] belongs_to :record, counter_cache: { touch: true }

Context

class Comment
  belongs_to :post, counter_cache: true
end

With the code above, every time a comment is added or removed from a post, the column comments_count in the posts table is increased or decreased.

When the comments_count of a Post is changed, its updated_at is not changed. That is because, by default, counter cache doesn’t touch the model.

Sometimes, though, it’s useful to also touch the Post, for instance if it’s displayed in a cached view and the cache is based on its updated_at. In those cases, this syntax works:

class Comment
  belongs_to :post, counter_cache: true, touch: true
end

Problem

The last snippet of code touches the parent Post every time a comment is changed. For instance, if the comment’s text changes, the parent Post is touched. Even though this snippet solves the problem (a cached Post becomes expired), it’s a little too much.

Feature proposal

class Comment
  belongs_to :post, counter_cache: { touch: true }
end

This code would only touch the parent Post when the counter cache column is updated.

Technical notes

The counter_cache option already accepts a Hash, see these lines of code.

If the Rails core team accepts this feature proposal, I can submit a PR which

  • changes the lines above to include touch = counter_cache.fetch(:touch, false)
  • change occurrences of reflection.options[:touch] in this line and this line to reflection.options[:touch] || reflection.options.dig(:counter_cache, :touch)

Thanks for the proposal! The use case makes sense. Being able to selectively touch a parent only when the counter changes, rather than on any child update, is a reasonable distinction.

Before we consider adding this to Rails core, could you clarify a couple of things?

Does this workaround cover your use case?

belongs_to :post, counter_cache: true

after_create  { post.touch }
after_destroy { post.touch }

This achieves the same behavior. The parent is only touched when a child is added or removed, not on other attribute changes, without any framework changes needed.

If that works, the core team will generally prefer keeping belongs_to options lean and pointing to callbacks for more specific behavior. But if there is a reason this does not work for you (e.g. you need this across many models, inside a gem, or have some other constraint), that context would help make the case for a first-class option.

1 Like