Logo

TODAY I LEARNED

Monthly streak display using SQL CTE in Rails

How to make a streak display like 750words.com

image

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.

Learned by obie-fernandez on Feb 10, 2021

973983 adforcodereview v3 0211021 c02 021121