caching-patterns
Skill211 starsChanged 5 months agoMITView on GitHub
Implements HTTP caching with ETags, fragment caching, Russian doll caching, and Solid Cache configuration. Use when optimizing performance, adding caching layers, or when user mentions ETags, fresh_when, stale?, cache keys, or Russian doll caching. WHEN NOT: For Turbo Stream real-time updates (use turbo-patterns), for background job cache warming logic (use job-patterns).
2 other projects use a version of this.
Adopted by
Most recently taken up by
- dbtinoy-/lexigram-framework-skillstheir own version, first seen Aug 24, 2026
- iCesofT/awesome-aitheir own version, first seen Apr 16, 2026
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 2
- dbtinoy-/lexigram-framework-skills★ 1same name
- iCesofT/awesome-ai★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository17 other files it gives its agents.
- AGENTS.mdAGENTS.md
- .claude/CLAUDE.mdCLAUDE.md
- .opencode/CLAUDE.mdCLAUDE.md
- api-patternsSkill
- auth-setupSkill
- concern-patternsSkill
- crud-patternsSkill
- event-trackingSkill
- job-patternsSkill
- mailer-patternsSkill
- migration-patternsSkill
- model-patternsSkill
- multi-tenant-setupSkill
- state-recordsSkill
- stimulus-patternsSkill
- testing-patternsSkill
- turbo-patternsSkill
The file
We split this skill into the rules it gives an agent
7 rules. About 0 are common, 5 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 132
Use counter caches to avoid N+1 in cache keys:
- Very specific: line 275
Use fresh_when for index and show actions
- Very specific: line 276
Use touch: true on associations for automatic invalidation
- Distinctive: line 277
Use Solid Cache in production (database-backed, no Redis)
- Distinctive: line 280
Use counter caches for counts
- Distinctive: line 289
Use Redis for caching (use Solid Cache)
- Distinctive: line 293
Use generic cache keys without version/timestamp
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Caching Patterns
Philosophy: Cache Aggressively, Invalidate Precisely
- HTTP caching with ETags and
fresh_whenfor free 304 Not Modified responses - Russian doll caching with
touch: truefor automatic cache invalidation - Fragment caching in views with cache keys based on
updated_attimestamps - Solid Cache (database-backed, no Redis) for production caching
- Collection caching with
cache_collectionfor lists - Low-level caching with
Rails.cache.fetchfor expensive computations
Project Knowledge
Stack: Solid Cache (database-backed), Turbo for page refreshes, ETags with conditional GET, fragment caching in ERB views, collection caching for lists.
Multi-tenancy: Cache keys scoped to account. URL-based:
app.myapp.com/123/projects/456.
Commands:
rails solid_cache:install # Install Solid Cache
rails db:migrate # Run cache migrations
rails cache:clear # Clear cacheCaching Strategy Hierarchy
Apply caching in this order (highest impact first):
- HTTP caching --
fresh_when/stale?in controllers (free 304s) - Fragment caching --
cacheblocks in views (Russian doll) - Collection caching --
cache_collectionfor lists of partials - Low-level caching --
Rails.cache.fetchfor expensive computations
Pattern 1: HTTP Caching with ETags
See @references/http-caching.md for full details.
# Single resource -- returns 304 if ETag matches
class BoardsController < ApplicationController
def show
@board = Current.account.boards.find(params[:id])
fresh_when @board
end
def index
@boards = Current.account.boards.includes(:creator)
fresh_when @boards
end
end
# Composite ETag from multiple objects
def show
fresh_when [@board, @card, Current.user]
end
# API with stale? for conditional rendering
def show
@board = Current.account.boards.find(params[:id])
if stale?(@board)
render json: @board
end
end
# Custom ETag with parameters
fresh_when etag: [@activities, @report_date, Current.user.timezone]Pattern 2: Russian Doll Caching
See @references/fragment-caching.md for full details.
Set up touch cascades in models:
class Card < ApplicationRecord
belongs_to :board, touch: true
end
class Comment < ApplicationRecord
belongs_to :card, touch: true
# Updating comment touches card -> touches board -> invalidates all caches
endNest cache blocks in views:
<% cache @board do %>
<h1><%= @board.name %></h1>
<% @board.columns.each do |column| %>
<% cache column do %>
<% column.cards.each do |card| %>
<% cache card do %>
<%= render card %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>Pattern 3: Collection Caching
<%# Cache each item individually with multi-fetch optimization %>
<% cache_collection @boards, partial: "boards/board" %>
<%# Manual alternative %>
<% @boards.each do |board| %>
<% cache board do %>
<%= render "boards/board", board: board %>
<% end %>
<% end %>Use counter caches to avoid N+1 in cache keys:
class Card < ApplicationRecord
belongs_to :board, counter_cache: true, touch: true
end
class Board < ApplicationRecord
def cache_key_with_version
"#{cache_key}/cards-#{cards_count}-#{updated_at.to_i}"
end
endPattern 4: Fragment Caching with Custom Keys
<%# Multiple dependencies %>
<% cache ["board_header", @board, Current.user] do %>
<h1><%= @board.name %></h1>
<% if Current.user.can_edit?(@board) %>
<%= link_to "Edit", edit_board_path(@board) %>
<% end %>
<% end %>
<%# With expiration %>
<% cache ["board_stats", @board], expires_in: 15.minutes do %>
<div class="stats"><%= @board.cards.count %> cards</div>
<% end %>
<%# Conditional caching %>
<% cache_if @enable_caching, board do %>
<%= board.name %>
<% end %>
<%# Multi-key with locale %>
<% cache ["dashboard", Current.account, Current.user,
@boards.maximum(:updated_at), I18n.locale] do %>
<%= render "boards_summary", boards: @boards %>
<% end %>Pattern 5: Low-Level Caching
class Board < ApplicationRecord
def statistics
Rails.cache.fetch([self, "statistics"], expires_in: 1.hour) do
{
total_cards: cards.count,
completed_cards: cards.joins(:closure).count,
total_comments: cards.joins(:comments).count
}
end
end
# Race condition protection for expensive operations
def expensive_calculation
Rails.cache.fetch(
[self, "expensive_calculation"],
expires_in: 1.hour,
race_condition_ttl: 10.seconds
) { calculate_complex_metrics }
end
# Version-based cache busting
STATS_VERSION = 2
def versioned_statistics
Rails.cache.fetch([self, "statistics", "v#{STATS_VERSION}"],
expires_in: 1.hour) { calculate_statistics }
end
endPattern 6: Cache Invalidation
See @references/cache-invalidation.md for full details.
# Prefer touch: true cascades (automatic)
belongs_to :board, touch: true
# Manual invalidation for low-level caches
class Card < ApplicationRecord
after_create_commit :clear_board_caches
after_destroy_commit :clear_board_caches
private
def clear_board_caches
Rails.cache.delete([board, "statistics"])
Rails.cache.delete([board, "card_distribution"])
end
end
# Sweeper pattern for batch invalidation
class CacheSweeper
def self.clear_board_caches(board)
Rails.cache.delete([board, "statistics"])
Rails.cache.delete([board, "card_distribution"])
Rails.cache.delete([board, "activity_summary", Date.current])
end
endPattern 7: Solid Cache Configuration
# config/environments/production.rb
config.cache_store = :solid_cache_store
# config/environments/development.rb
config.cache_store = :memory_store, { size: 64.megabytes }
# config/environments/test.rb
config.cache_store = :null_storePattern 8: Cache Warming
class CacheWarmerJob < ApplicationJob
queue_as :low_priority
def perform(account)
account.boards.find_each do |board|
board.statistics
board.card_distribution
end
end
end
# config/recurring.yml
cache:
daily_refresh:
class: DailyCacheRefreshJob
schedule: every day at 3am
queue: low_priorityBoundaries
Always
- Use
fresh_whenfor index and show actions - Use
touch: trueon associations for automatic invalidation - Use Solid Cache in production (database-backed, no Redis)
- Include
expires_infor time-based data - Scope cache keys to account in multi-tenant apps
- Use counter caches for counts
- Eager load associations to prevent N+1 queries
Ask First
- Whether to cache user-specific content
- Cache expiration times (freshness vs performance)
- Whether to warm caches in background jobs
Never
- Use Redis for caching (use Solid Cache)
- Cache without considering invalidation strategy
- Forget
touch: truewith Russian doll caching - Cache CSRF tokens or sensitive user data
- Use generic cache keys without version/timestamp
- Cache in test environment (use
:null_store) - Cache across account boundaries in multi-tenant apps
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

