ruby 41 lines · 7 steps

Building a daily order totals report in Rails

A single grouped SQL query aggregates completed orders per day, then reshapes the rows into a clean JSON payload.

Explained by highlit
1module Api
2 module Reports
3 class DailyOrderTotalsController < Api::BaseController
4 def index
5 totals = Order
6 .completed
7 .where(created_at: reporting_range)
8 .group(Arel.sql("DATE(orders.created_at)"))
9 .order(Arel.sql("DATE(orders.created_at)"))
10 .pluck(
11 Arel.sql("DATE(orders.created_at)"),
12 Arel.sql("COUNT(*)"),
13 Arel.sql("COALESCE(SUM(orders.total_cents), 0)")
14 )
15 
16 render json: {
17 currency: "USD",
18 from: reporting_range.begin.to_date,
19 to: reporting_range.end.to_date,
20 days: totals.map do |date, count, revenue_cents|
21 {
22 date: date,
23 order_count: count,
24 revenue: (revenue_cents.to_d / 100).round(2)
25 }
26 end
27 }
28 end
29 
30 private
31 
32 def reporting_range
33 from = params[:from].present? ? Date.parse(params[:from]) : 30.days.ago.to_date
34 to = params[:to].present? ? Date.parse(params[:to]) : Date.current
35 from.beginning_of_day..to.end_of_day
36 rescue ArgumentError
37 raise ActionController::BadRequest, "invalid date parameter"
38 end
39 end
40 end
41end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Pushing grouping and aggregation into SQL lets the database do the heavy lifting instead of loading rows into Ruby.
  2. 2pluck returns raw tuples, so mapping them into named hashes keeps the JSON shape explicit and controlled.
  3. 3Parsing user-supplied dates in one place with a rescue centralizes validation and error responses.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a daily order totals report in Rails — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code