-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
138: Caching Strategies in Rails (Fragment, Russian Doll)
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
@usercache is invalidated. - Rails starts re-rendering the dashboard.
- When it hits the
cache projectblock 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:
- Modify the
Taskmodel so that saving a task automatically updates theupdated_attimestamp of its associatedWorkspace. - Update the
workspaces/show.html.erbview 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 thetaskobject. - Verify your logic: Explain in a comment within your code why the
touch: trueis necessary for the@workspace` cache to invalidate.
There are no comments for now.