Implements Rails caching patterns for performance optimization. Use when adding fragment caching, Russian doll caching, low-level caching, cache invalidation, or when working with caching, cache keys, or memoization.
Rails provides multiple caching layers:
# config/environments/development.rb
config.action_controller.perform_caching = true
config.cache_store = :memory_store
# config/environments/production.rb
config.cache_store = :solid_cache_store # Rails 8 default
# OR
config.cache_store = :redis_cache_store, { url: ENV["REDIS_URL"] }
Enable caching in development:
bin/rails dev:cache
| Store | Use Case | Pros | Cons |
|---|---|---|---|
:memory_store | Development | Fast, no setup | Not shared, limited size |
:solid_cache_store | Production (Rails 8) | Database-backed, no Redis | Slightly slower |
:redis_cache_store | Production | Fast, shared | Requires Redis |
:file_store | Simple production | Persistent, no Redis | Slow, not shared |
:null_store | Testing | No caching | N/A |
<%# app/views/events/_event.html.erb %>
<% cache event do %>
<article class="event-card">
<h3><%= event.name %></h3>
<p><%= event.description %></p>
<time><%= l(event.event_date, format: :long) %></time>
<%= render event.venue %>
</article>
<% end %>
Rails generates cache keys from:
updated_at timestamp# Generated key example:
# views/events/123-20240115120000000000/abc123digest
<%# With version %>
<% cache [event, "v2"] do %>
...
<% end %>
<%# With user-specific content %>
<% cache [event, current_user] do %>
...
<% end %>
<%# With explicit key %>
<% cache "featured-events-#{Date.current}" do %>
<%= render @featured_events %>
<% end %>
Nested caches where inner caches are reused when outer cache is invalidated:
<%# app/views/events/show.html.erb %>
<% cache @event do %>
<h1><%= @event.name %></h1>
<section class="vendors">
<% @event.vendors.each do |vendor| %>
<% cache vendor do %>
<%= render partial: "vendors/card", locals: { vendor: vendor } %>
<% end %>
<% end %>
</section>
<section class="comments">
<% @event.comments.each do |comment| %>
<% cache comment do %>
<%= render comment %>
<% end %>
<% end %>
</section>
<% end %>
Use touch: true on belongs_to associations to cascade invalidation up the chain.
<%# Caches each item individually %>
<%= render partial: "events/event", collection: @events, cached: true %>
<%# With custom cache key %>
<%= render partial: "events/event",
collection: @events,
cached: ->(event) { [event, current_user.admin?] } %>
Use Rails.cache.fetch with a block for the most common pattern:
# Basic fetch
Rails.cache.fetch("user_#{user.id}_stats", expires_in: 1.hour) do
user.calculate_stats
end
# In service objects
class EventStatsService
def call(event)
Rails.cache.fetch(["event_stats", event], expires_in: 15.minutes) do
{ comments: event.comments.count, vendors: event.vendors.count }
end
end
end
Three strategies:
expires_in: 1.hourupdated_at timestamp in cache keyRails.cache.delete("key") or Rails.cache.delete_matched("pattern*")Use touch: true for Russian doll cascade:
class Comment < ApplicationRecord
belongs_to :event, touch: true
end
Use stale? for conditional GET (ETags/Last-Modified) and expires_in for Cache-Control headers:
def show
@event = Event.find(params[:id])
if stale?(@event)
respond_to do |format|
format.html
format.json { render json: @event }
end
end
end
Use a :caching metadata tag to enable caching in specs:
# spec/rails_helper.rb
RSpec.configure do |config|
config.around(:each, :caching) do |example|
caching = ActionController::Base.perform_caching
ActionController::Base.perform_caching = true
example.run
ActionController::Base.perform_caching = caching
end
end
touch: true on belongs_to for Russian dollcached: true