Delegation attributes can confuse your cache invalidation logic

This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the caching category.

Last Updated: 2021-05-16

I had this caching code in a view. It used a common pattern of invalidating a cache whenever any fields on the underlying object change:

<% cache(@product) do %>
   @product.description
<% end %>

Yet after changing a product's description in the UI, the description shown in HTML was still wrong, i.e. the old version was showing. Why wasn't the cache invalidated?

The issue was this: The description was delegated to another model. As such, the product model was unchanged, therefore the cache was not invalidated.

Here's how the delegation happened in the Product model

class Product
  delegate :name, :exam_year, :grade, :description, :page_count, to: :subject
end

Solutions

Option 1: Expand the cache key to include any models for which the contents of the cache block has delegated attributes to.

  <% cache[@product, @product.subject] %>

Option 2: Use special features of your framework to handle cache invalidation for you.

class Subject
  has_one :product, touch: true
end