skills/rails-caching-patterns/SKILL.md
Caching patterns for Rails applications including fragment caching, low-level caching, HTTP caching, Russian doll caching, and cache invalidation strategies. Automatically invoked when working with Rails.cache, cache stores, stale?/fresh_when, fragment caching, cache keys, or performance optimization through caching. Triggers on "cache", "caching", "Rails.cache", "fragment cache", "Russian doll", "stale?", "fresh_when", "cache key", "cache store", "Redis cache", "Solid Cache", "memcached", "ETag", "cache invalidation", "cache bust". NOT for CDN configuration (use rails-devops-patterns) or database query optimization (use rails-model-patterns).
npx skillsauth add ag0os/rails-dev-plugin rails-caching-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Analyze and recommend caching strategies across all layers of a Rails application.
Follow standard Rails conventions for basic cache helper, Rails.cache.fetch, stale?/fresh_when, and expires_in. Focus on the opinionated patterns below.
See patterns.md for detailed code examples.
| Pattern | Use When |
|---------|----------|
| Russian doll + touch: true | Nested associations — inner updates bust outer caches |
| race_condition_ttl | High-traffic keys — prevents cache stampede on expiry |
| fetch_multi | Multiple cache reads — one round-trip instead of N |
| Collection caching (cached: true) | Rendering collections — uses read_multi internally |
| Solid Cache vs Redis | Match the configured store; default Solid Cache unless Redis is already in the stack |
| Composite cache keys | Varying by user, locale, or version |
The cache store is an orthogonal project fact, not an architecture choice. If the project already configures one, match it (project-conventions fingerprint). For a new choice, pick by what is already in the stack:
| Store | Use when | Why |
|-------|----------|-----|
| config.cache_store = :solid_cache_store | No Redis in the stack | No external deps, database-backed, Rails 8 default |
| config.cache_store = :redis_cache_store, { url: ENV["REDIS_URL"] } | Redis already running (Sidekiq, ActionCable) | Reuse existing infrastructure |
| :memory_store | Development | Toggle with bin/rails dev:cache |
cache_key_with_version change when data changesrack-mini-profiler or server timing — don't cache what isn't slowtouch: true propagates changes up the association chainThe critical setup: touch: true on associations propagates updated_at changes upward.
class Comment < ApplicationRecord
belongs_to :post, touch: true
end
class Post < ApplicationRecord
belongs_to :category, touch: true
has_many :comments, dependent: :destroy
end
<% cache @category do %>
<h2><%= @category.name %></h2>
<% @category.posts.includes(:comments).each do |post| %>
<% cache post do %>
<h3><%= post.title %></h3>
<% post.comments.each do |comment| %>
<% cache comment do %>
<p><%= comment.body %></p>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
How it works: Comment updated -> post.touch -> category.touch. Only changed fragments re-render; unchanged siblings hit cache.
Prevents cache stampede when many requests hit an expired key simultaneously. First request recalculates; others get stale data for the TTL window:
Rails.cache.fetch("dashboard/stats", expires_in: 5.minutes, race_condition_ttl: 30.seconds) do
DashboardStats.calculate # Expensive
end
read_multiOne cache round-trip for the entire collection:
<%= render partial: "products/product", collection: @products, cached: true %>
fetch_multi for Batch Cache ReadsAvoid N cache round-trips:
class Product < ApplicationRecord
def self.with_cached_stats(products)
keys = products.map { |p| "product/#{p.id}/stats" }
cached = Rails.cache.fetch_multi(*keys, expires_in: 1.hour) do |key|
product_id = key.split("/")[1].to_i
calculate_stats_for(product_id)
end
products.each do |product|
product.cached_stats = cached["product/#{product.id}/stats"]
end
end
end
# Varies by multiple factors
cache [@product, current_user.locale, "v2"] do ... end
# Conditional caching — skip cache for admins
cache_if !current_user&.admin?, @product do ... end
Update cache proactively when data changes instead of waiting for expiry:
class Product < ApplicationRecord
after_commit :update_cache, on: [:create, :update]
after_commit :clear_cache, on: :destroy
def cached_stats
Rails.cache.fetch(stats_cache_key, expires_in: 1.day) do
calculate_stats
end
end
private
def update_cache
Rails.cache.write(stats_cache_key, calculate_stats, expires_in: 1.day)
end
def clear_cache
Rails.cache.delete(stats_cache_key)
end
def stats_cache_key = "product/#{id}/stats"
end
| Anti-Pattern | Fix |
|-------------|-----|
| Caching without measuring | Profile first with rack-mini-profiler |
| Manual invalidation everywhere | Use key-based expiration (cache_key_with_version) |
| No touch: true with Russian doll | Parent cache never busts — add touch: true |
| Long expires_in without race_condition_ttl | Cache stampede on expiry — add race_condition_ttl: 30.seconds |
| Rails.cache.fetch in a loop | N round-trips — use fetch_multi |
| Caching nil results | Thundering herd — return null object or empty result |
| User-specific content in shared cache | Data leaks — include user/session in cache key |
When recommending caching, provide:
development
WHAT: Language-agnostic corrective guidance for the refactoring phase. WHEN: Agent is restructuring code, fixing code smells, reducing complexity, or improving maintainability. NOT FOR: Writing new features, debugging runtime errors, performance tuning, or object design decisions.
tools
Analyzes Rails view templates, partials, layouts, helpers, and form patterns for best practices. Use when reviewing ERB templates, improving view performance with fragment caching, fixing form helpers, organizing partials, adding accessibility attributes, or evaluating collection rendering. NOT for Stimulus/Turbo logic (use hotwire-patterns), controller concerns, or API-only responses.
testing
Analyzes Rails test suites and recommends testing best practices for RSpec and Minitest. Use when writing new tests, reviewing test coverage, fixing flaky tests, improving test performance, choosing between test types (unit, integration, system, request), or setting up factories and fixtures. NOT for production monitoring, deployment verification, or load/stress testing infrastructure.
development
Detects a Rails project's architecture axes — logic placement (native vs extracted) and delivery (html vs api) — so other skills load profile-appropriate guidance without inline conditionals. Use when planning architecture or when a recommendation depends on where business logic lives or whether the app renders HTML or serves JSON. NOT for test framework, job backend, cache store, or auth library choices — those are orthogonal facts detected by project-conventions.