Implements HTTP caching with ETags, fresh_when, stale?, and fragment caching. Use when optimizing performance, adding caching layers, or when user mentions ETags, HTTP caching, fresh_when, stale?, cache keys, or Russian doll caching.
How this skill is triggered — by the user, by Claude, or both
Slash command
/rails-37signals-patterns:37signals-cachingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are an expert Rails developer who implements aggressive caching strategies following modern Rails codebases. You use HTTP caching (ETags, conditional GET), Russian doll caching, fragment caching, and Solid Cache for a fast, database-backed caching layer.
You are an expert Rails developer who implements aggressive caching strategies following modern Rails codebases. You use HTTP caching (ETags, conditional GET), Russian doll caching, fragment caching, and Solid Cache for a fast, database-backed caching layer.
Approach:
fresh_when for free 304 Not Modified responsescache_collection and cache_key_with_versionRails.cache.fetchvs. Traditional Approach:
# ❌ BAD: No caching at all
class BoardsController < ApplicationController
def show
@board = Board.find(params[:id])
@cards = @board.cards.includes(:comments)
end
end
# ❌ BAD: Redis for caching (not database-backed)
config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'] }
# ❌ BAD: Manual cache invalidation
after_update do
Rails.cache.delete("board_#{id}")
Rails.cache.delete("board_#{id}_cards")
end
# ❌ BAD: Generic cache keys
cache "board_#{@board.id}" do
# ...
end
Good Way:
# ✅ GOOD: HTTP caching with ETags
class BoardsController < ApplicationController
def show
@board = Board.find(params[:id])
fresh_when @board
end
end
# ✅ GOOD: Solid Cache (database-backed)
config.cache_store = :solid_cache_store
# ✅ GOOD: Russian doll caching with automatic invalidation
class Card < ApplicationRecord
belongs_to :board, touch: true
end
class Board < ApplicationRecord
has_many :cards
end
# ✅ GOOD: Fragment caching with cache keys
<% cache @board do %>
<% @board.cards.each do |card| %>
<% cache card do %>
<%= render card %>
<% end %>
<% end %>
<% end %>
# ✅ GOOD: Cache expensive computations
def statistics
Rails.cache.fetch([self, "statistics"], expires_in: 1.hour) do
calculate_statistics
end
end
Rails Version: 8.2 (edge) Stack:
Authentication:
Multi-tenancy:
Related Skills:
$37signals-model - Touch associations for cache invalidation$37signals-turbo - Works with caching for fast page updates$37signals-jobs - Cache warming and preloading jobs$37signals-migration - Solid Cache table setup# Install Solid Cache (already in Rails 8)
rails solid_cache:install
# Generate cache migrations
rails generate solid_cache:install
# Run cache migrations
rails db:migrate
# Clear cache
rails cache:clear
# Cache stats
rails solid_cache:stats
Use conditional GET to send 304 Not Modified when content hasn't changed.
# app/controllers/boards_controller.rb
class BoardsController < ApplicationController
def show
@board = Current.account.boards.find(params[:id])
# Returns 304 if ETag matches
fresh_when @board
end
def index
@boards = Current.account.boards.includes(:creator)
# ETag based on collection
fresh_when @boards
end
end
# app/controllers/cards_controller.rb
class CardsController < ApplicationController
before_action :set_board
before_action :set_card, only: [:show, :edit, :update]
def show
# Composite ETag from multiple objects
fresh_when [@board, @card, Current.user]
end
def index
@cards = @board.cards.includes(:creator, :comments)
# Collection ETag
fresh_when @cards
end
private
def set_board
@board = Current.account.boards.find(params[:board_id])
end
def set_card
@card = @board.cards.find(params[:id])
end
end
# app/controllers/api/v1/boards_controller.rb
class Api::V1::BoardsController < Api::V1::BaseController
def show
@board = Current.account.boards.find(params[:id])
# Set both ETag and Last-Modified
if stale?(@board)
render json: @board
end
end
def index
@boards = Current.account.boards.order(updated_at: :desc)
# Conditional GET with custom cache key
if stale?(etag: @boards, last_modified: @boards.maximum(:updated_at))
render json: @boards
end
end
end
Custom ETags:
# app/controllers/reports_controller.rb
class ReportsController < ApplicationController
def activity
@report_date = params[:date]&.to_date || Date.current
@activities = Current.account.activities
.where(created_at: @report_date.beginning_of_day..@report_date.end_of_day)
# Custom ETag incorporating parameters
fresh_when etag: [@activities, @report_date, Current.user.timezone]
end
def dashboard
@boards = Current.account.boards.includes(:cards)
@recent_activity = Current.account.activities.recent
# Composite ETag from multiple collections
fresh_when etag: [
@boards.maximum(:updated_at),
@recent_activity.maximum(:updated_at),
Current.user.preferences_updated_at
]
end
end
Use nested fragment caching with automatic invalidation through touch: true.
# app/models/board.rb
class Board < ApplicationRecord
belongs_to :account
belongs_to :creator
has_many :cards, dependent: :destroy
has_many :columns, dependent: :destroy
end
# app/models/card.rb
class Card < ApplicationRecord
belongs_to :board, touch: true # Updates board.updated_at
belongs_to :column, touch: true
belongs_to :creator
has_many :comments, dependent: :destroy
end
# app/models/comment.rb
class Comment < ApplicationRecord
belongs_to :card, touch: true # Updates card.updated_at → board.updated_at
belongs_to :creator
# Automatically invalidates card cache when comment changes
end
View caching:
<%# app/views/boards/show.html.erb %>
<% cache @board do %>
<h1><%= @board.name %></h1>
<p><%= @board.description %></p>
<div class="columns">
<% @board.columns.each do |column| %>
<% cache column do %>
<div class="column">
<h2><%= column.name %></h2>
<div class="cards">
<% column.cards.each do |card| %>
<% cache card do %>
<%= render card %>
<% end %>
<% end %>
</div>
</div>
<% end %>
<% end %>
</div>
<% end %>
<%# app/views/cards/_card.html.erb %>
<% cache card do %>
<div class="card" id="<%= dom_id(card) %>">
<h3><%= card.title %></h3>
<p><%= card.description %></p>
<div class="comments">
<% card.comments.each do |comment| %>
<% cache comment do %>
<%= render comment %>
<% end %>
<% end %>
</div>
</div>
<% end %>
How it works:
# When you update a comment:
comment.update!(body: "Updated text")
# Rails automatically:
# 1. Updates comment.updated_at
# 2. Touches card.updated_at (because touch: true)
# 3. Touches board.updated_at (cascades through touch: true)
# 4. Invalidates all 3 cache fragments
# Cache keys look like:
# views/boards/123-20250117120000000000/...
# views/cards/456-20250117120100000000/...
# views/comments/789-20250117120100000000/...
Cache collections of records efficiently with cache_collection.
# app/views/boards/index.html.erb
<div class="boards">
<%# Cache each board individually %>
<% cache_collection @boards, partial: "boards/board" %>
</div>
<%# app/views/boards/_board.html.erb %>
<%# This partial is rendered for each board %>
<div class="board" id="<%= dom_id(board) %>">
<h2><%= board.name %></h2>
<p><%= board.description %></p>
<div class="meta">
<%= board.cards.count %> cards
</div>
</div>
<%# Alternative: Manual cache per item %>
<div class="boards">
<% @boards.each do |board| %>
<% cache board do %>
<%= render "boards/board", board: board %>
<% end %>
<% end %>
</div>
Collection with counter cache:
# app/models/board.rb
class Board < ApplicationRecord
has_many :cards, dependent: :destroy
# Avoid N+1 queries in cache keys
def cache_key_with_version
"#{cache_key}/cards-#{cards_count}-#{updated_at.to_i}"
end
end
# app/models/card.rb
class Card < ApplicationRecord
belongs_to :board, counter_cache: true, touch: true
end
# db/migrate/xxx_add_cards_count_to_boards.rb
class AddCardsCountToBoards < ActiveRecord::Migration[8.0]
def change
add_column :boards, :cards_count, :integer, default: 0, null: false
reversible do |dir|
dir.up { Board.find_each { |b| Board.reset_counters(b.id, :cards) } }
end
end
end
Collection caching with scope:
# app/controllers/boards_controller.rb
class BoardsController < ApplicationController
def index
@boards = Current.account.boards
.includes(:creator)
.order(updated_at: :desc)
# Cache the entire collection
@cached_boards = Rails.cache.fetch(
["boards_index", Current.account, @boards.maximum(:updated_at)],
expires_in: 1.hour
) do
@boards.to_a
end
end
end
Use custom cache keys for complex scenarios.
# app/views/boards/show.html.erb
<%# Cache with multiple dependencies %>
<% cache ["board_header", @board, Current.user] do %>
<div class="board-header">
<h1><%= @board.name %></h1>
<% if Current.user.can_edit?(@board) %>
<%= link_to "Edit", edit_board_path(@board) %>
<% end %>
</div>
<% end %>
<%# Cache with custom expiration %>
<% cache ["board_stats", @board], expires_in: 15.minutes do %>
<div class="board-stats">
<div class="stat">
<span class="label">Cards</span>
<span class="value"><%= @board.cards.count %></span>
</div>
<div class="stat">
<span class="label">Comments</span>
<span class="value"><%= @board.cards.joins(:comments).count %></span>
</div>
</div>
<% end %>
<%# Cache with skip_digest option for dynamic content %>
<% cache ["board_activity", @board], skip_digest: true do %>
<%= render @board.activities.recent %>
<% end %>
Conditional caching:
# app/views/boards/_board.html.erb
<% cache_if @enable_caching, board do %>
<div class="board">
<%= board.name %>
</div>
<% end %>
# app/views/boards/show.html.erb
<% cache_unless Current.user.admin?, @board do %>
<%= render @board %>
<% end %>
Multi-key caching:
# app/views/dashboards/show.html.erb
<% cache [
"dashboard",
Current.account,
Current.user,
@boards.maximum(:updated_at),
@projects.maximum(:updated_at),
I18n.locale
] do %>
<div class="dashboard">
<%= render "boards_summary", boards: @boards %>
<%= render "projects_summary", projects: @projects %>
</div>
<% end %>
Cache database queries and computations with Rails.cache.fetch.
# app/models/board.rb
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,
active_members: cards.joins(:assignments).distinct.count(:user_id)
}
end
end
def card_distribution
Rails.cache.fetch([self, "card_distribution"], expires_in: 30.minutes) do
columns.includes(:cards).map { |column|
{
name: column.name,
count: column.cards.count,
percentage: (column.cards.count.to_f / cards.count * 100).round(1)
}
}
end
end
def recent_activity_summary
Rails.cache.fetch(
[self, "activity_summary", Date.current],
expires_in: 5.minutes
) do
activities.where(created_at: 24.hours.ago..)
.group(:subject_type)
.count
end
end
end
# app/models/account.rb
class Account < ApplicationRecord
def monthly_metrics(month = Date.current)
Rails.cache.fetch(
[self, "monthly_metrics", month.beginning_of_month],
expires_in: 1.day
) do
{
boards_created: boards.where(created_at: month.all_month).count,
cards_created: cards.where(created_at: month.all_month).count,
comments_added: comments.where(created_at: month.all_month).count,
active_users: activities.where(created_at: month.all_month)
.distinct.count(:creator_id)
}
end
end
def search_results(query)
Rails.cache.fetch(
["search", self, query.downcase.strip],
expires_in: 10.minutes
) do
{
boards: boards.where("name ILIKE ?", "%#{query}%").limit(10),
cards: cards.where("title ILIKE ?", "%#{query}%").limit(20),
comments: comments.where("body ILIKE ?", "%#{query}%").limit(20)
}
end
end
end
Cache with race condition protection:
# app/models/board.rb
class Board < ApplicationRecord
def expensive_calculation
Rails.cache.fetch(
[self, "expensive_calculation"],
expires_in: 1.hour,
race_condition_ttl: 10.seconds
) do
# Expensive operation
sleep 5
calculate_complex_metrics
end
end
end
Cache with versioning:
# app/models/board.rb
class Board < ApplicationRecord
STATS_VERSION = 2 # Increment to bust all caches
def statistics
Rails.cache.fetch(
[self, "statistics", "v#{STATS_VERSION}"],
expires_in: 1.hour
) do
calculate_statistics
end
end
end
Explicit cache clearing for complex dependencies.
# app/models/board.rb
class Board < ApplicationRecord
has_many :cards, dependent: :destroy
after_update :clear_statistics_cache, if: :significant_change?
def clear_statistics_cache
Rails.cache.delete([self, "statistics"])
Rails.cache.delete([self, "card_distribution"])
end
def refresh_cache
clear_statistics_cache
statistics # Regenerate
card_distribution # Regenerate
end
private
def significant_change?
saved_change_to_name? || saved_change_to_description?
end
end
# app/models/card.rb
class Card < ApplicationRecord
belongs_to :board, touch: true
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
# app/models/concerns/cacheable.rb
module Cacheable
extend ActiveSupport::Concern
included do
after_commit :clear_associated_caches
end
def clear_associated_caches
# Override in models
end
def cache_key_with_account
[Current.account, cache_key_with_version]
end
end
Sweeper pattern for batch invalidation:
# app/models/cache_sweeper.rb
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])
# Clear related caches
board.account.tap do |account|
Rails.cache.delete([account, "monthly_metrics", Date.current.beginning_of_month])
end
end
def self.clear_account_caches(account)
Rails.cache.delete_matched("accounts/#{account.id}/*")
end
def self.clear_user_caches(user)
Rails.cache.delete([user, "preferences"])
Rails.cache.delete([user, "permissions"])
Rails.cache.delete([user, "recent_boards"])
end
end
# Usage in models
class Board < ApplicationRecord
after_update :sweep_caches
private
def sweep_caches
CacheSweeper.clear_board_caches(self)
end
end
Configure database-backed caching with Solid Cache.
# config/environments/production.rb
Rails.application.configure do
# Use Solid Cache (database-backed)
config.cache_store = :solid_cache_store
# Optional: Configure specific database
# config.solid_cache.connects_to = { database: { writing: :cache } }
end
# config/environments/development.rb
Rails.application.configure do
# Use memory store in development
config.cache_store = :memory_store, { size: 64.megabytes }
# Or use Solid Cache in development too
# config.cache_store = :solid_cache_store
end
# config/environments/test.rb
Rails.application.configure do
# Use null store in tests (no caching)
config.cache_store = :null_store
# Or use memory store to test caching behavior
# config.cache_store = :memory_store
end
Solid Cache migration:
# db/migrate/xxx_create_solid_cache_entries.rb
# Generated by: rails solid_cache:install
class CreateSolidCacheEntries < ActiveRecord::Migration[8.0]
def change
create_table :solid_cache_entries, id: :uuid do |t|
t.binary :key, null: false, limit: 1024
t.binary :value, null: false, limit: 512.megabytes
t.datetime :created_at, null: false
t.index :key, unique: true
t.index :created_at
end
end
end
Cache store helpers:
# app/models/concerns/cache_helper.rb
module CacheHelper
extend ActiveSupport::Concern
class_methods do
def cache_fetch(key, **options, &block)
Rails.cache.fetch(
[name.underscore, key],
**default_cache_options.merge(options),
&block
)
end
def default_cache_options
{ expires_in: 1.hour, race_condition_ttl: 5.seconds }
end
end
def cache_fetch(key, **options, &block)
Rails.cache.fetch(
[self, key],
**self.class.default_cache_options.merge(options),
&block
)
end
end
# Usage
class Board < ApplicationRecord
include CacheHelper
def statistics
cache_fetch("statistics") do
calculate_statistics
end
end
end
Warm caches in background jobs for better performance.
# app/jobs/cache_warmer_job.rb
class CacheWarmerJob < ApplicationJob
queue_as :low_priority
def perform(account)
account.boards.find_each do |board|
warm_board_cache(board)
end
end
private
def warm_board_cache(board)
board.statistics
board.card_distribution
board.recent_activity_summary
end
end
# app/jobs/daily_cache_refresh_job.rb
class DailyCacheRefreshJob < ApplicationJob
queue_as :low_priority
def perform
Account.find_each do |account|
# Refresh monthly metrics for current month
account.monthly_metrics(Date.current)
# Warm dashboard caches
account.boards.recent.limit(10).each do |board|
board.statistics
end
end
end
end
# config/recurring.yml
cache:
daily_refresh:
class: DailyCacheRefreshJob
schedule: every day at 3am
queue: low_priority
Preload in controller:
# app/controllers/dashboards_controller.rb
class DashboardsController < ApplicationController
def show
@boards = Current.account.boards.recent.limit(10)
# Warm caches in parallel
@boards.each do |board|
CacheWarmerJob.perform_later(board)
end
# Use cached data
@board_stats = @boards.map { |board|
{
board: board,
stats: board.statistics
}
}
end
end
Combine caching with eager loading to prevent N+1 queries.
# app/controllers/boards_controller.rb
class BoardsController < ApplicationController
def show
@board = Current.account.boards
.includes(
cards: [:creator, :column, { comments: :creator }]
)
.find(params[:id])
fresh_when @board
end
def index
@boards = Current.account.boards
.includes(:creator, :cards)
.order(updated_at: :desc)
fresh_when @boards
end
end
# app/models/board.rb
class Board < ApplicationRecord
# Preload associations for caching
scope :with_cached_associations, -> {
includes(:creator, cards: [:creator, :comments])
}
def cached_cards
Rails.cache.fetch([self, "cards_with_associations"], expires_in: 5.minutes) do
cards.includes(:creator, :comments).to_a
end
end
end
Counter caches:
# app/models/card.rb
class Card < ApplicationRecord
belongs_to :board, counter_cache: true, touch: true
has_many :comments, dependent: :destroy
end
# app/models/comment.rb
class Comment < ApplicationRecord
belongs_to :card, counter_cache: true, touch: true
end
# db/migrate/xxx_add_counters.rb
class AddCounters < ActiveRecord::Migration[8.0]
def change
add_column :boards, :cards_count, :integer, default: 0, null: false
add_column :cards, :comments_count, :integer, default: 0, null: false
reversible do |dir|
dir.up do
Board.find_each { |b| Board.reset_counters(b.id, :cards) }
Card.find_each { |c| Card.reset_counters(c.id, :comments) }
end
end
add_index :boards, :cards_count
add_index :cards, :comments_count
end
end
Cache Turbo Frames for partial page updates.
# app/views/boards/show.html.erb
<%= turbo_frame_tag "board_header" do %>
<% cache [@board, "header"] do %>
<h1><%= @board.name %></h1>
<p><%= @board.description %></p>
<% end %>
<% end %>
<%= turbo_frame_tag "board_cards" do %>
<% cache [@board, "cards"] do %>
<div class="cards">
<% cache_collection @board.cards, partial: "cards/card" %>
</div>
<% end %>
<% end %>
<%= turbo_frame_tag "board_activity" do %>
<%# Don't cache real-time activity %>
<div class="activity">
<%= render @board.activities.recent %>
</div>
<% end %>
Lazy-loaded cached frames:
<%# app/views/boards/show.html.erb %>
<%= turbo_frame_tag "board_statistics", src: board_statistics_path(@board) do %>
<p>Loading statistics...</p>
<% end %>
<%# app/views/boards/statistics.html.erb %>
<%= turbo_frame_tag "board_statistics" do %>
<% cache [@board, "statistics"], expires_in: 15.minutes do %>
<div class="statistics">
<%= render "boards/statistics", board: @board %>
</div>
<% end %>
<% end %>
<%# app/controllers/boards/statistics_controller.rb %>
class Boards::StatisticsController < ApplicationController
def show
@board = Current.account.boards.find(params[:board_id])
fresh_when [@board, "statistics"]
end
end
Test caching behavior and cache invalidation.
# test/models/board_test.rb
require "test_helper"
class BoardTest < ActiveSupport::TestCase
test "touching card updates board updated_at" do
board = boards(:design)
card = cards(:one)
assert_changes -> { board.reload.updated_at } do
card.touch
end
end
test "statistics are cached" do
board = boards(:design)
# First call calculates
assert_queries(5) { board.statistics }
# Second call uses cache
assert_no_queries { board.statistics }
end
test "statistics cache is cleared after card update" do
board = boards(:design)
card = cards(:one)
# Warm cache
board.statistics
# Update card
card.update!(title: "New title")
# Cache should be cleared
assert_nil Rails.cache.read([board, "statistics"])
end
test "cache key includes updated_at" do
board = boards(:design)
original_key = board.cache_key_with_version
board.touch
assert_not_equal original_key, board.cache_key_with_version
end
end
# test/controllers/boards_controller_test.rb
require "test_helper"
class BoardsControllerTest < ActionDispatch::IntegrationTest
test "returns 304 when board unchanged" do
board = boards(:design)
get board_url(board)
assert_response :success
etag = response.headers["ETag"]
get board_url(board), headers: { "If-None-Match" => etag }
assert_response :not_modified
end
test "returns 200 when board updated" do
board = boards(:design)
get board_url(board)
etag = response.headers["ETag"]
board.touch
get board_url(board), headers: { "If-None-Match" => etag }
assert_response :success
end
test "conditional GET with stale board" do
board = boards(:design)
get board_url(board)
assert_response :success
last_modified = response.headers["Last-Modified"]
# Update board
board.update!(name: "New name")
get board_url(board), headers: { "If-Modified-Since" => last_modified }
assert_response :success
end
end
# test/integration/caching_test.rb
require "test_helper"
class CachingTest < ActionDispatch::IntegrationTest
test "board page uses fragment caching" do
board = boards(:design)
get board_url(board)
assert_response :success
# Check that cache was written
assert Rails.cache.exist?(board)
end
test "updating card invalidates board cache" do
board = boards(:design)
card = cards(:one)
# Warm cache
get board_url(board)
original_cache_key = board.cache_key_with_version
# Update card (touches board)
card.update!(title: "New title")
# Cache key should change
assert_not_equal original_cache_key, board.reload.cache_key_with_version
end
test "Russian doll caching invalidates parent caches" do
board = boards(:design)
card = cards(:one)
comment = comments(:one)
# Warm all caches
get board_url(board)
# Update comment (should touch card → board)
comment.update!(body: "New comment")
# All cache keys should be different
assert_changes -> { board.reload.updated_at } do
assert_changes -> { card.reload.updated_at } do
comment.touch
end
end
end
end
# test/jobs/cache_warmer_job_test.rb
require "test_helper"
class CacheWarmerJobTest < ActiveJob::TestCase
test "warms board caches" do
account = accounts(:acme)
board = boards(:design)
CacheWarmerJob.perform_now(account)
# Caches should be populated
assert Rails.cache.exist?([board, "statistics"])
assert Rails.cache.exist?([board, "card_distribution"])
end
end
# Single resource
fresh_when @board
# Collection
fresh_when @boards
# Composite
fresh_when [@board, @card, Current.user]
# With custom key
fresh_when etag: [@board, params[:view]], last_modified: @board.updated_at
# Simple
<% cache @board do %>
<%= render @board %>
<% end %>
# With expiration
<% cache @board, expires_in: 15.minutes do %>
<%= expensive_render @board %>
<% end %>
# Custom key
<% cache ["board", @board, Current.user] do %>
<%= render @board %>
<% end %>
# Collection
<% cache_collection @boards, partial: "boards/board" %>
# Basic
Rails.cache.fetch([self, "key"]) { expensive_operation }
# With expiration
Rails.cache.fetch([self, "key"], expires_in: 1.hour) { expensive_operation }
# With race condition protection
Rails.cache.fetch([self, "key"], expires_in: 1.hour, race_condition_ttl: 10.seconds) do
expensive_operation
end
# Touch associations
belongs_to :board, touch: true
# Manual deletion
Rails.cache.delete([self, "key"])
# Callbacks
after_update :clear_cache
belongs_to :board, counter_cache: true, touch: true
@boards = Board.includes(:creator, cards: :comments)
Rails.cache.fetch([self, "complex_query"], expires_in: 1.hour) do
complex_calculation
end
fresh_when @board # Returns 304 if unchanged
# Nested caches with touch: true for automatic invalidation
<% cache @board do %>
<% cache @card do %>
<%= render @card %>
<% end %>
<% end %>
<% cache_collection @boards, partial: "boards/board" %>
# Don't cache if content varies by user
<% cache_unless Current.user.admin?, @board do %>
<%= render @board %>
<% end %>
fresh_when for index and show actionstouch: true on associations for automatic cache invalidationupdated_at timestampscache_collection for listsexpires_in for time-based expirationtouch: true when using Russian doll cachingexpires_in for time-sensitive datanpx claudepluginhub joshyorko/agent-skillsProvides a checklist for code reviews covering functionality, security, performance, maintainability, tests, and quality. Use for pull requests, audits, team standards, and developer training.