Introduction
Low-level caching lets you store any Ruby object — not just view fragments — using the Rails.cache API. This is essential for caching expensive database queries, API responses, and computed values.
Key Concepts
- Rails.cache.fetch: The primary caching method — reads from cache if present, otherwise executes the block, stores the result, and returns it.
- Expiration: Time-based cache invalidation using
expires_in. - Cache Keys: Strings or arrays used to identify cached values. Arrays are joined with slashes.
Real World Context
A dashboard that computes trending posts by joining views, likes, and comments can take 500ms+ to query. Caching the result for 15 minutes reduces load on every page view to near zero while keeping data reasonably fresh.
Deep Dive
Rails.cache.fetch
ruby# Read from cache or compute and store posts = Rails.cache.fetch('popular_posts', expires_in: 1.hour) do Post.popular.limit(10).to_a end
Caching Expensive Queries
rubyclass Post < ApplicationRecord def self.trending Rails.cache.fetch('trending_posts', expires_in: 15.minutes) do joins(:views) .where('views.created_at > ?', 24.hours.ago) .group(:id) .order('COUNT(views.id) DESC') .limit(10) .to_a # Convert to array before caching end end end
Caching External API Calls
rubyclass WeatherService def self.current(city) Rails.cache.fetch("weather/#{city}", expires_in: 30.minutes) do response = HTTP.get("https://api.weather.com/#{city}") JSON.parse(response.body) end end end
Cache Invalidation
rubyRails.cache.delete('popular_posts') Rails.cache.clear # Clear entire cache
Object-Based Keys
rubyclass User < ApplicationRecord def dashboard_stats Rails.cache.fetch([self, 'dashboard_stats'], expires_in: 5.minutes) do { posts_count: posts.count, followers_count: followers.count } end end end
Common Pitfalls
- Caching ActiveRecord relations instead of arrays — Always call
.to_abefore caching a query result. Caching the relation object stores a query, not the data. - Forgetting to invalidate — Time-based expiration works for most cases, but if data changes frequently, combine it with explicit
Rails.cache.deletecalls.
Best Practices
- Use fetch, not read/write —
fetchcombines both operations atomically and is the idiomatic Rails approach. - Set reasonable expiration times — Balance freshness and performance. Dashboard stats might tolerate 5-minute staleness; user profiles might need 1-minute expiry.
Summary
Rails.cache.fetchis the primary caching method — read or compute and store.- Always convert query results to arrays with
.to_abefore caching. - Use object-based cache keys like
[user, 'stats']for automatic invalidation. - Set
expires_into control staleness tolerance.
Code Examples
ruby
# Cache an expensive query for 15 minutes
trending = Rails.cache.fetch('trending_posts', expires_in: 15.minutes) do
Post.joins(:views)
.where('views.created_at > ?', 24.hours.ago)
.group(:id)
.order('COUNT(views.id) DESC')
.limit(10)
.to_a
end