ruby 50 lines · 7 steps

Memoized report metrics in Ruby in Rails

A report object computes each metric once and caches it, so a single summary call reuses shared queries.

Explained by highlit
1class ReportGenerator
2 def initialize(account, period)
3 @account = account
4 @period = period
5 end
6 
7 def summary
8 {
9 revenue: total_revenue,
10 churn_rate: churn_rate,
11 active_customers: active_customers.size,
12 top_products: top_products
13 }
14 end
15 
16 def total_revenue
17 @total_revenue ||= paid_invoices.sum(&:amount_cents) / 100.0
18 end
19 
20 def churn_rate
21 return @churn_rate if defined?(@churn_rate)
22 
23 starting = @account.customers.active_at(@period.begin).count
24 @churn_rate = starting.zero? ? 0.0 : (cancelled_count.to_f / starting).round(4)
25 end
26 
27 def active_customers
28 @active_customers ||= @account.customers.active_during(@period).to_a
29 end
30 
31 private
32 
33 def paid_invoices
34 @paid_invoices ||= @account.invoices.paid.where(issued_at: @period)
35 end
36 
37 def cancelled_count
38 @cancelled_count ||= @account.subscriptions.cancelled_during(@period).count
39 end
40 
41 def top_products
42 @top_products ||= paid_invoices
43 .flat_map(&:line_items)
44 .group_by(&:product_id)
45 .transform_values { |items| items.sum(&:amount_cents) }
46 .sort_by { |_, total| -total }
47 .first(5)
48 .map(&:first)
49 end
50end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Memoizing each metric method turns repeated calls into cheap lookups after the first computation.
  2. 2Use defined? instead of ||= when a legitimate result can be falsey like 0.0.
  3. 3Grouping metric logic into one object lets several methods share the same underlying query cheaply.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Memoized report metrics in Ruby in Rails — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code