Skip to Content
Course content

138: Caching Strategies in Rails (Fragment, Russian Doll)

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I once spent three hours debugging a "ghost" in a production app where a client complained that their dashboard wasn't updating after they edited a project description. I checked the database—the data was there. I cleared the browser cache—no luck. Then I looked at the view code and saw this:

<% cache @user do %>
  <div class="dashboard">
    <h1>Welcome, <%= @user.name %>!</h1>
    <%= render @user.projects %>
  </div>
</% cache %>

The developer had cached the entire dashboard block using the @user object as the cache key. It seemed logical: if the user changes, the cache should change. But here was the problem: when a Project was updated, the User record remained untouched. Since the @user.cache_key didn't change, Rails kept serving the old, cached HTML of the dashboard, completely ignoring the updated projects inside it.

The stale content trap

This is the most common mistake I see with fragment caching. You're essentially telling Rails, "Only recalculate this HTML if the @user object's updated_at timestamp changes." But your dashboard depends on multiple objects. If you have a nested relationship, the parent doesn't automatically know when a child has changed.

To fix this, we need a way to tell the parent record to "refresh" its timestamp whenever a child is modified. In Rails, we do this using the touch option on the association.

Triggering updates with touch: true

Go into your Project model (the child) and update the belongs_to relationship like this:

class Project < ApplicationRecord
  belongs_to :user, touch: true
end

By adding touch: true, every time a project is saved, updated, or destroyed, Rails will automatically reach up to the associated User and update their updated_at timestamp. This changes the @user.cache_key, which invalidates the fragment cache, and Rails will render the fresh HTML on the next request. It's a simple line of code, but it's the glue that makes caching actually work in a relational database.

Nesting for performance: Russian Doll Caching

Now, updating the parent every time a child changes is great for correctness, but if you have a massive dashboard with 100 projects, you're still re-rendering all 100 projects every time just one of them changes. That's where "Russian Doll" caching comes in. You wrap your caches inside other caches.

Think of it like a set of nesting dolls. The outer doll is the user dashboard; the inner dolls are the individual projects.

<% cache @user do %>
  <div class="dashboard">
    <h1>Welcome, <%= @user.name %>!</h1>
    
    <div class="projects-list">
      <% @user.projects.each do |project| %>
        <% cache project do %>
          <%= render project %>
        </% cache %>
      <% end %>
    </div>
  </div>
</% cache %>

Here is why this is powerful:

  • If a project is updated, it "touches" the user. The outer @user cache is invalidated.
  • Rails starts re-rendering the dashboard.
  • When it hits the cache project block for the projects that didn't change, Rails finds those fragments already in the cache and pulls them instantly.
  • Only the one project that actually changed gets re-rendered.

I'll give you a pro tip: avoid putting highly dynamic data—like a "Time since last login" or a "Current Shopping Cart Total"—inside these fragment caches. If the content changes every single second or depends on the session rather than the database record, caching it will either lead to stale data or a cache that expires so often it becomes a performance burden.




📋 Practical Task

Implementing Russian Doll Caching for a Project Management Dashboard

You are working on a Project Management app where a Workspace has many Tasks. Currently, the workspace view is slow because it renders hundreds of tasks, and you've noticed that updates to tasks aren't reflecting on the workspace page until the server restarts.

Your Task:

  1. Modify the Task model so that saving a task automatically updates the updated_at timestamp of its associated Workspace.
  2. Update the workspaces/show.html.erb view to implement Russian Doll caching. You must wrap the entire workspace container in a cache block keyed to the @workspace, and wrap the individual task rendering in a cache block keyed to the task object.
  3. Verify your logic: Explain in a comment within your code why the touch: true is necessary for the @workspace` cache to invalidate.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.