ruby
36 lines · 5 steps
Caching dashboard metrics in Rails
A service object wraps expensive account queries in Rails.cache with time-bucketed keys so results stay fresh but cheap.
Explained by
highlit
1class DashboardMetrics
2 CACHE_TTL = 15.minutes
3
4 def initialize(account)
5 @account = account
6 end
7
8 def monthly_recurring_revenue
9 Rails.cache.fetch(mrr_cache_key, expires_in: CACHE_TTL) do
10 @account.subscriptions
11 .active
12 .joins(:plan)
13 .sum("plans.amount_cents * subscriptions.quantity")
14 end
15 end
16
17 def active_users_count
18 Rails.cache.fetch(active_users_cache_key, expires_in: CACHE_TTL) do
19 @account.users.where("last_seen_at > ?", 30.days.ago).count
20 end
21 end
22
23 private
24
25 def mrr_cache_key
26 ["account", @account.id, "mrr", current_bucket].join("/")
27 end
28
29 def active_users_cache_key
30 ["account", @account.id, "active_users", current_bucket].join("/")
31 end
32
33 def current_bucket
34 (Time.current.to_i / CACHE_TTL.to_i)
35 end
36end
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Wrapping expensive aggregate queries in Rails.cache.fetch trades a little staleness for large read savings.
- 2Embedding a time bucket in the cache key gives you automatic expiry aligned to a window without relying solely on TTL.
- 3A plain service object keeps caching logic out of models and controllers where it would otherwise sprawl.
Related explainers
ruby
class Booking < ApplicationRecord belongs_to :room belongs_to :guest
Guarding booking dates in a Rails model
validations
associations
scopes
Intermediate
5 steps
ruby
class LRUCache def initialize(capacity) raise ArgumentError, "capacity must be positive" unless capacity.positive?
How an LRU cache works in Ruby
caching
eviction
hash-ordering
Intermediate
7 steps
ruby
class QueryParser def self.parse(query_string) new(query_string).parse end
Parsing nested query strings in Ruby
parsing
recursion
tokenization
Intermediate
9 steps
ruby
class Article < ApplicationRecord belongs_to :author, class_name: "User" has_many :taggings, dependent: :destroy has_many :tags, through: :taggings
How scopes compose in Rails
scopes
activerecord
query-composition
Intermediate
8 steps
python
import json import hashlib from datetime import timedelta
Idempotent payment endpoints in FastAPI
idempotency
redis
distributed-locking
Advanced
9 steps
ruby
class Order class InvalidTransition < StandardError; end TRANSITIONS = {
A state machine for order transitions in Ruby
state-machine
data-driven
error-handling
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/caching-dashboard-metrics-in-rails-explained-ruby-10d1/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.