class Streak

Represents a streak of calendar days as computed by a date column on an association.

So for example if you have a User that has_many :posts, then +Streak.new(user, :posts, :created_at).length+ will tell you how many consecutive days a given user created posts.

Attributes

association[R]

the AR association through which we want to grab a column to caculate a streak

column[R]

an AR column resolving to a date. the column that we want to calculate a calendar date streak against

except_today[R]

whether to include today in the streak length calculation or not. If this is true, then you are assuming there is still time today for the streak to be extended

instance[R]

the base ActiveRecord object instance for this streak calculation

Public Class Methods

new(instance, association, column, except_today: false) click to toggle source

Creates a new Streak

@param [ActiveRecord::Base] instance an ActiveRecord object instance @param [Symbol] association a key representing the ActiveRecord association on the instance @param [Symbol] column a key representing the column on the association that you want to calculate the streak against @param [Boolean] except_today whether to include today in the streak length calculation or not. If this is true, then you are assuming there is still time today for the streak to be extended

# File lib/streakable/streak.rb, line 28
def initialize(instance, association, column, except_today: false)
  @instance = instance
  @association = association
  @column = column
  # Don't penalize the current day being absent when determining streaks
  @except_today = except_today
end

Public Instance Methods

length(longest: false) click to toggle source

Calculate the length of this calendar day streak

@param [Boolean] longest if true, calculate the longest day streak in the sequence, not just the current one

# File lib/streakable/streak.rb, line 40
def length(longest: false)
  # no streaks
  if streaks.empty?
    0

  # calculate the longest one?
  elsif longest
    streaks.sort do |x, y|
      y.size <=> x.size
    end.first.size

  # default streak calculation
  else
    # pull the first streak
    streak = streaks.first
    
    # either the streak includes today,
    # or we don't care about today and it includes yesterday
    if streak.include?(Date.current) || except_today && streak.include?(Date.current - 1.day)
      streak.size
    else
      0
    end
  end
end
streaks() click to toggle source

Get a list of all calendar day streaks, sorted descending (from most recent to farthest away) Includes 1-day streaks. If you want to filter the results further, for example if you want 2 only include 2+ day streaks, you'll have to filter on the result

# File lib/streakable/streak.rb, line 71
def streaks
  return [] if days.empty?

  streaks = []
  streak = []
  days.each.with_index do |day, i|
    # first day
    if i == 0
      # since this is the first one,
      # push to our new streak
      streak << day

    # consecutive day, the previous day was "tomorrow"
    # relative to day (since we're date descending)
    elsif days[i-1] == (day+1.day)
      # push to existing streak
      streak << day

    # streak was broken
    else
      # push our current streak
      streaks << streak

      # start a new streak
      # and push day to our new streak
      streak = []
      streak << day
    end

    # the jig is up, push the current streak
    if i == (days.size-1) 
      streaks << streak 
    end
  end
 
  streaks
end

Private Instance Methods

days() click to toggle source

TODO: add class methods/scopes to calculate streaks, days scrap code from old method below:

date_strings = instance.send(association).order(column => :desc).pluck(column) dates = date_strings.map(&:to_date) dates.sort.reverse.uniq

# File lib/streakable/streak.rb, line 117
def days
  @days ||= begin
    instance.send(association).map do |x|
      x.send(column).in_time_zone.to_date
    end.sort do |x, y|
      x <=> y
    end.reverse.uniq
  end
end