dchuk/rails_ai_agents · Archived

caching-strategies

Implements Rails caching patterns for performance optimization. Use when adding fragment caching, Russian doll caching, low-level caching, HTTP caching with ETags, cache invalidation, or when user mentions caching, performance, cache keys, or Solid Cache.

First seen Feb 13, 2026

Installation

$ npx skills add dchuk/rails_ai_agents --skill caching-strategies

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from dchuk/rails_ai_agents · top by installs.

npx skills add dchuk/rails_ai_agents

Browse all from dchuk/rails_ai_agents

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

License LICENSE
Default branch main
Status Archived

Skill metadata

Parsed from SKILL.md frontmatter.

Allowed toolsRead, Write, Edit, Bash, Glob, Grep

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 7,374 B
  • docs SUMMARY.md 281 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 2 installs

SKILL.md

Caching Strategies for Rails 8

Overview

Rails provides multiple caching layers:

  • HTTP caching: ETags and fresh_when for 304 Not Modified
  • Fragment caching: Cache view partials
  • Russian doll caching: Nested cache fragments with touch: true
  • Low-level caching: Cache arbitrary data with Rails.cache.fetch
  • Collection caching: Efficient cached rendering of collections
  • Solid Cache: Database-backed caching (Rails 8 default, no Redis)

Cache Store Options

Store Use Case
:memory_store Development
:solidcachestore Production (Rails 8 default)
:rediscachestore Production (if Redis available)
:null_store Testing
# config/environments/production.rb
config.cache_store = :solid_cache_store

# config/environments/development.rb
config.cache_store = :memory_store

Enable caching in development:

bin/rails dev:cache

HTTP Caching (ETags / fresh_when)

Use conditional GET to send 304 Not Modified when content has not changed.

class EventsController < ApplicationController
  def show
    @event = current_account.events.find(params[:id])
    fresh_when @event
  end

  def index
    @events = current_account.events.recent
    fresh_when @events
  end
end

Composite ETags

def show
  @event = current_account.events.find(params[:id])
  fresh_when [@event, Current.user]
end

With stale? for JSON

class Api::EventsController < Api::BaseController
  def show
    @event = current_account.events.find(params[:id])
    if stale?(@event)
      render json: @event
    end
  end
end

Fragment Caching

<%# 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>
  </article>
<% end %>

Custom Cache Keys

<% cache [event, "v2"] do %>
  ...
<% end %>

<% cache [event, current_user] do %>
  ...
<% end %>

Russian Doll Caching

Nested caches with automatic invalidation through touch: true:

# app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :event, touch: true
end
<% cache @event do %>
  <h1><%= @event.name %></h1>
  <% @event.comments.each do |comment| %>
    <% cache comment do %>
      <%= render comment %>
    <% end %>
  <% end %>
<% end %>

When a comment is updated, touch: true cascades up through updated_at timestamps, invalidating all parent caches automatically.

Collection Caching

<%# Caches each item individually, multi-read from cache store %>
<%= render partial: "events/event", collection: @events, cached: true %>

Low-Level Caching

Rails.cache.fetch("stats/#{Date.current}", expires_in: 1.hour) do
  { total_events: Event.count, total_revenue: Order.sum(:total_cents) }
end

In Models

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
end

With Race Condition Protection

Rails.cache.fetch([self, "stats"], expires_in: 1.hour, race_condition_ttl: 10.seconds) do
  expensive_operation
end

Cache Invalidation

Key-Based (Automatic)

Cache keys include updated_at, so updates automatically expire old entries.

Touch Cascade

class Card < ApplicationRecord
  belongs_to :board, touch: true  # Updates board.updated_at
end

class Comment < ApplicationRecord
  belongs_to :card, touch: true   # Updates card.updated_at -> board.updated_at
end

Manual Invalidation

class Event < ApplicationRecord
  after_commit :invalidate_caches

  private

  def invalidate_caches
    Rails.cache.delete([self, "statistics"])
    Rails.cache.delete("featured_events")
  end
end

Sweeper Pattern

class CacheSweeper
  def self.clear_board_caches(board)
    Rails.cache.delete([board, "statistics"])
    Rails.cache.delete([board, "card_distribution"])
  end
end

Counter Caching

# Migration
add_column :events, :vendors_count, :integer, default: 0, null: false

# Model
class Vendor < ApplicationRecord
  belongs_to :event, counter_cache: true
end

# Usage (no query needed)
event.vendors_count

Cache Warming

class CacheWarmerJob < ApplicationJob
  queue_as :low

  def perform(account)
    account.boards.find_each do |board|
      board.statistics
      board.card_distribution
    end
  end
end

Testing Caching

# test/test_helper.rb (enable caching for specific tests)
class ActiveSupport::TestCase
  def with_caching(&block)
    caching = ActionController::Base.perform_caching
    ActionController::Base.perform_caching = true
    Rails.cache.clear
    yield
  ensure
    ActionController::Base.perform_caching = caching
  end
end

Testing Touch Cascade

# test/models/card_test.rb
require "test_helper"

class CardCachingTest < ActiveSupport::TestCase
  test "touching card updates board updated_at" do
    board = boards(:one)
    card = cards(:one)

    assert_changes -> { board.reload.updated_at } do
      card.touch
    end
  end
end

Testing HTTP Caching

# test/controllers/boards_controller_test.rb
require "test_helper"

class BoardsControllerCachingTest < ActionDispatch::IntegrationTest
  setup do
    sign_in users(:one)
    @board = boards(:one)
  end

  test "returns 304 when board unchanged" do
    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
    get board_url(@board)
    etag = response.headers["ETag"]

    @board.touch

    get board_url(@board), headers: { "If-None-Match" => etag }
    assert_response :success
  end
end

Testing Cache Invalidation

# test/models/board_test.rb
require "test_helper"

class BoardCacheInvalidationTest < ActiveSupport::TestCase
  test "statistics cache is cleared after card update" do
    board = boards(:one)
    card = cards(:one)

    board.statistics # Warm cache

    card.update!(title: "New title")

    assert_nil Rails.cache.read([board, "statistics"])
  end
end

Memoization

class EventPresenter < BasePresenter
  def vendor_count
    @vendor_count ||= event.vendors.count
  end
end

Checklist

  • Cache store configured for environment
  • fresh_when on show/index actions
  • touch: true on belongs_to for Russian doll
  • Collection caching with cached: true
  • Low-level caching for expensive queries
  • Cache invalidation strategy defined
  • Counter caches for counts
  • Cache warming jobs for cold starts
  • All tests GREEN