Monthly streak display using SQL CTE in Rails
How to make a streak display like 750words.com
The database can do the heavy lifting for this kind of thing. A CTE (Common Table Expression) in a WITH
clause to give me the set of days of the month that I would fill in with the completion data using a LEFT JOIN
to make sure I got rows back even when there was no corresponding data on the right side.
# app/models/streak.rb
Streak = Struct.new(:user, :date) do
# If you're wondering why we didn't use class extension syntax of Struct see the following link
# https://tiagoamaro.com.br/2016/03/05/superclass-mismatch-structs-and-unicorn/
SQL = "WITH dates(d) AS (
SELECT generate_series(
(date ?)::timestamp,
(date ?)::timestamp,
interval '1 day')
)
SELECT dates.d::date created_at, count(e.id) count FROM dates
LEFT JOIN entries e on dates.d::date = e.created_at::date AND e.user_id = ?
GROUP BY dates.d::date
ORDER BY dates.d::date"
def calendar
Entry.find_by_sql([SQL, date.beginning_of_month, date.end_of_month, user.id])
end
end
I'm knowingly abusing ActiveRecord by shoving the data I want to represent into Entry
objects. I can be kinder to my future self and other maintainers by subsequently wrapping the returned objects into something more descriptive like StreakMonth
or whatever.