streakable 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2117453eb59848b55a11e27607df503b4a3e8c32
4
- data.tar.gz: b88852d4a47c1f5b20290d0706e2f5284e108bf8
3
+ metadata.gz: 726ca092eee86d7af43a51665e0293cb6cb9f4de
4
+ data.tar.gz: 92c319a54b0593658eb97abed907bb815ae0a007
5
5
  SHA512:
6
- metadata.gz: 92cc3cdf95b76c1df41c4d49f469ec0703cda4d96cd62c373c390de98fc96d939666b13a11edce31fd5d7bf2ab078c044fa3bca8388bbf1f0c5a22471ca47e0b
7
- data.tar.gz: 438bcda9fd55f5470cb9d973547fcfabdbc11a6f48d88fae8ad392f464bd8b0a1ac1f047357aaadcbf15fcc0b42013652de96624b12d3ab8c08dbbdd40d11ee1
6
+ metadata.gz: b70d48925fa792efd5337f0810d4cd81eb2edadf122ee0ac5ca77182a549f1b30cc332435e83077bb7d51f896652f10a127ed8d279e7126e89efded5c0ceb337
7
+ data.tar.gz: 75662d9a63b49f3b4191a9a1434f73ae24967ad543d5702e9ad5c24b05313758e82598a10bc46f7e912f14c89b5e73d63700bec2e4e72d54b3cc0c0228247d6d
data/.travis.yml CHANGED
@@ -5,7 +5,6 @@ matrix:
5
5
  branches:
6
6
  only: master
7
7
  rvm:
8
- - 2.0.0
9
8
  - 2.1.0
10
9
  - 2.2.0
11
10
  - 2.3.0
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
- [![Build Status](https://travis-ci.org/szTheory/streakable.svg?branch=master)](https://travis-ci.org/szTheory/streakable)
1
+ [![Gem Version](https://badge.fury.io/rb/streakable.svg)](https://badge.fury.io/rb/streakable) [![Build Status](https://travis-ci.org/szTheory/streakable.svg?branch=master)](https://travis-ci.org/szTheory/streakable) [![Inline docs](http://inch-ci.org/github/szTheory/streakable.svg?branch=master)](http://inch-ci.org/github/szTheory/streakable) [![MIT License](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/szTheory/streakable/blob/master/LICENSE.txt)
2
2
 
3
3
  # Streakable
4
4
 
5
- Track consecutive day streaks on your ActiveRecord models. Fork of [has_streak](https://github.com/garrettqmartin8/has_streak) by Garrett Martin. This version has a different include interface and more options.
5
+ Ruby gem to track consecutive day streaks :calendar: on your Rails/ActiveRecord models. Fork of [has_streak](https://github.com/garrettqmartin8/has_streak) by Garrett Martin. This version has a different include interface and more options. Requires Ruby >= 2.1 and ActiveRecord >= 3.0. GitHub project page: https://github.com/szTheory/streakable
6
6
 
7
7
  ## Installation
8
8
 
@@ -28,12 +28,10 @@ class User < ActiveRecord::Base
28
28
  end
29
29
  ```
30
30
 
31
- I want to track how many days in a row that each user wrote a post. I just have to add <code>streakable</code> to the model:
31
+ I want to track how many days in a row that each user wrote a post. I just have to include <code>streakable</code> in the model:
32
32
 
33
33
  ```ruby
34
34
  class User < ActiveRecord::Base
35
- # ...
36
-
37
35
  include Streakable
38
36
  end
39
37
  ```
@@ -50,22 +48,39 @@ The <code>streak</code> instance method can be called with any association:
50
48
  user.streak(:other_association)
51
49
  ```
52
50
 
53
- And you can change the column the streak is calculated on
51
+ And you can change the column the streak is calculated on:
54
52
 
55
53
  ```ruby
56
54
  user.streak(:posts, :updated_at)
57
55
  ```
58
56
 
59
- Don't penalize the current day being absent when determining streaks
57
+ Don't penalize the current day being absent when determining streaks (the User could write another Post before the day ends):
60
58
 
61
59
  ```ruby
62
60
  user.streak(:posts, except_today: true)
63
61
  ```
64
62
 
63
+ Find the longest streak, not just the current one:
64
+
65
+ ```ruby
66
+ user.streak(:posts, longest: true)
67
+ ```
68
+
69
+ To get all of the streaks, not just the current one:
70
+
71
+ ```ruby
72
+ user.streaks(:posts)
73
+ ```
74
+
65
75
  ## Contributing
66
76
 
67
77
  1. Fork it
68
- 2. Create your feature branch (`git checkout -b my-new-feature`)
78
+ 2. Create your feature branch (`git checkout -b feature/my-new-feature`)
69
79
  3. Commit your changes (`git commit -am 'Add some feature'`)
70
- 4. Push to the branch (`git push origin my-new-feature`)
71
- 5. Create new Pull Request
80
+ 4. Push to the branch (`git push origin feature/my-new-feature`)
81
+ 5. Make sure specs are passing (`bundle exec rspec`)
82
+ 6. Create new Pull Request
83
+
84
+ ## License
85
+
86
+ See the [LICENSE](https://github.com/szTheory/streakable/blob/master/LICENSE.txt) file.
data/_config.yml ADDED
@@ -0,0 +1 @@
1
+ theme: jekyll-theme-tactile
data/lib/streakable.rb CHANGED
@@ -1,6 +1,29 @@
1
1
  require "streakable/streak"
2
2
  require "active_record"
3
3
 
4
+ # Let's say I have a +User+ that +has_many+ posts:
5
+ #
6
+ # class User < ActiveRecord::Base
7
+ # has_many :posts
8
+ # end
9
+ #
10
+ # I want to track how many days in a row that each user wrote a post. I just have to +include Streakable+ in the model:
11
+ #
12
+ # class User < ActiveRecord::Base
13
+ # include Streakable
14
+ # end
15
+ #
16
+ # Now I can display the user's streak:
17
+ # user.streak(:posts) # => number of days in a row that this user wrote a post (as determined by the created_at column, by default)
18
+ #
19
+ # The +streak+ instance method can be called with any association:
20
+ # user.streak(:other_association)
21
+ #
22
+ # And you can change the column the streak is calculated on:
23
+ # user.streak(:posts, :updated_at)
24
+ #
25
+ # Don't penalize the current day being absent when determining streaks (the User could write another Post before the day ends):
26
+ # user.streak(:posts, except_today: true)
4
27
  module Streakable
5
28
  def self.included(klass)
6
29
  klass.class_eval do
@@ -8,9 +31,42 @@ module Streakable
8
31
  end
9
32
  end
10
33
 
11
- module InstanceMethods
12
- def streak(association, column=:created_at, except_today: false)
13
- Streak.new(self, association, column, except_today).length
34
+ module InstanceMethods # :nodoc:
35
+ # Calculate a calendar streak. That is to say, the number of consecutive
36
+ # days that exist for some date column, on an asociation with this object.
37
+ #
38
+ # For example if you have a User with many :posts, and one was created
39
+ # today that would be a streak of 1. If that user also created a Post yesterday,
40
+ # then the streak would be 2. If he created another Post the day before that one,
41
+ # he'd have a streak of 3, etc.
42
+ #
43
+ # On the other hand imagine that the User hasn't created a Post yet today, but
44
+ # he did create one yesterday. Is that a streak? By default, it would be a streak of 0,
45
+ # so no. If you want to exclude the current day from this calculation, and count from
46
+ # yesterday, set +except_today+ to +true+.
47
+ #
48
+ # @param [Symbol] association the ActiveRecord association on the instance
49
+ # @param [Symbol] column the column on the association that you want to calculate the streak against.
50
+ # @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
51
+ # @param [Boolean] longest if true, calculate the longest day streak in the sequence, not just the current one
52
+ def streak(association, column=:created_at, except_today: false, longest: false)
53
+ build_streak(association, column, except_today: except_today).length(longest: longest)
14
54
  end
55
+
56
+ # Calculate all calendar streaks. Returns a list of Date arrays. That is to say, a listing of consecutive
57
+ # days counts that exist for some date column, on an asociation with this object.
58
+ #
59
+ # @param [Symbol] association the ActiveRecord association on the instance
60
+ # @param [Symbol] column the column on the association that you want to calculate the streak against.
61
+ # @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
62
+ # @param [Boolean] longest if true, calculate the longest day streak in the sequence, not just the current one
63
+ def streaks(association, column=:created_at)
64
+ build_streak(association, column, except_today: true).streaks
65
+ end
66
+
67
+ private
68
+ def build_streak(association, column, except_today:)
69
+ Streak.new(self, association, column, except_today: except_today)
70
+ end
15
71
  end
16
72
  end
@@ -1,6 +1,31 @@
1
+ # Represents a streak of calendar days as computed
2
+ # by a date column on an association.
3
+ #
4
+ # So for example if you have a User that has_many :posts, then
5
+ # +Streak.new(user, :posts, :created_at).length+ will tell you how many
6
+ # consecutive days a given user created posts.
1
7
  class Streak
2
- attr_reader :instance, :association, :column, :except_today
3
- def initialize(instance, association, column, except_today)
8
+ # the base ActiveRecord object instance for this streak calculation
9
+ attr_reader :instance
10
+
11
+ # the AR association through which we want to grab a column to caculate a streak
12
+ attr_reader :association
13
+
14
+ # an AR column resolving to a date. the column that we want to calculate a calendar date streak against
15
+ attr_reader :column
16
+
17
+ # whether to include today in the streak length
18
+ # calculation or not. If this is true, then you are assuming there
19
+ # is still time today for the streak to be extended
20
+ attr_reader :except_today
21
+
22
+ # Creates a new Streak
23
+ #
24
+ # @param [ActiveRecord::Base] instance an ActiveRecord object instance
25
+ # @param [Symbol] association a key representing the ActiveRecord association on the instance
26
+ # @param [Symbol] column a key representing the column on the association that you want to calculate the streak against
27
+ # @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
28
+ def initialize(instance, association, column, except_today: false)
4
29
  @instance = instance
5
30
  @association = association
6
31
  @column = column
@@ -8,41 +33,70 @@ class Streak
8
33
  @except_today = except_today
9
34
  end
10
35
 
11
- def length
12
- @length ||= begin
13
- val = 0
14
- streak_map.each do |map_bool|
15
- break if !map_bool
16
- val += 1
36
+ # Calculate the length of this calendar day streak
37
+ #
38
+ # @param [Boolean] longest if true, calculate the longest day streak in the sequence,
39
+ # not just the current one
40
+ def length(longest: false)
41
+ if streaks.empty?
42
+ 0
43
+ elsif longest
44
+ streaks.sort.first.size
45
+ else
46
+ streak = streaks.first
47
+ if streak.include?(Date.current) || except_today && streak.include?(Date.yesterday)
48
+ streak.size
49
+ else
50
+ 0
17
51
  end
18
-
19
- val
20
52
  end
21
53
  end
22
54
 
23
- private
24
- def days
25
- @days ||= instance.send(association).order(column => :desc).pluck(column).map(&:to_date).uniq
26
- end
55
+ # Get a list of all calendar day streaks, sorted descending
56
+ # (from most recent to farthest away)
57
+ # Includes 1-day streaks. If you want to filter
58
+ # the results further, for example if you want 2 only
59
+ # include 2+ day streaks, you'll have to filter on the result
60
+ def streaks
61
+ return [] if days.empty?
27
62
 
28
- def streak_map
29
- @streak_map || begin
30
- days.map.with_index do |day, i|
31
- if i == 0
32
- streak_day(day)
33
- else
34
- days[i-1] == day.tomorrow
35
- end
36
- end
37
- end
38
- end
63
+ streaks = []
64
+ streak = []
65
+ days.each.with_index do |day, i|
66
+ # first day
67
+ if i == 0
68
+ # since this is the first one,
69
+ # push to our new streak
70
+ streak << day
39
71
 
40
- def streak_day(day)
41
- if !except_today
42
- day == Date.current
72
+ # consecutive day, the previous day was "tomorrow"
73
+ # relative to day (since we're date descending)
74
+ elsif days[i-1] == day.tomorrow
75
+ # push to existing streak
76
+ streak << day
77
+
78
+ # streak was broken
43
79
  else
44
- day == Date.current ||
45
- day == Date.yesterday
80
+ # push our current streak
81
+ streaks << streak
82
+
83
+ # start a new streak
84
+ # and push day to our new streak
85
+ streak = []
86
+ streak << day
87
+ end
88
+
89
+ # the jig is up, push the current streak
90
+ if i == (days.size-1)
91
+ streaks << streak
46
92
  end
47
93
  end
94
+
95
+ streaks
96
+ end
97
+
98
+ private
99
+ def days
100
+ @days ||= instance.send(association).order(column => :desc).pluck(column).map(&:to_date).uniq
101
+ end
48
102
  end
@@ -1,24 +1,24 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Streakable do
4
+ let(:user) { User.create }
5
+
6
+ let!(:posts) do
7
+ post_dates.each do |date|
8
+ user.posts.create(created_at: date)
9
+ end
10
+ end
11
+ let(:post_dates) { [] }
12
+
4
13
  describe "#streak" do
5
14
  subject { user.streak(:posts) }
6
15
 
7
- let(:user) { User.create }
8
-
9
- context "when a user has no posts" do
16
+ context "user has no posts" do
10
17
  before { expect(user.posts).to be_empty }
11
18
  it { is_expected.to be 0 }
12
19
  end
13
20
 
14
- let!(:posts) do
15
- post_dates.each do |date|
16
- user.posts.create(created_at: date)
17
- end
18
- end
19
- let(:post_dates) { [] }
20
-
21
- context "when a user posted on each of the last three days" do
21
+ context "user posted on each of the last three days" do
22
22
  let(:post_dates) { [2.days.ago, 1.day.ago, DateTime.current] }
23
23
 
24
24
  it "returns the streak" do
@@ -26,8 +26,37 @@ describe Streakable do
26
26
  end
27
27
  end
28
28
 
29
- context "when a user didn't post today" do
29
+ context "user only has a post today" do
30
+ let(:post_dates) { [DateTime.current] }
31
+ before do
32
+ expect(user.posts.size).to eql(1)
33
+ expect(user.posts.first.created_at.to_date).to eql(Date.current)
34
+ end
35
+
36
+ it "returns the streak of 1" do
37
+ expect(subject).to eq(1)
38
+ end
39
+ end
40
+
41
+ context "user has streak today, and a longer streak before that" do
42
+ let(:post_dates) { [5.days.ago, 4.days.ago, 3.days.ago, 1.day.ago, DateTime.current] }
43
+
44
+ it "still returns the current streak" do
45
+ expect(subject).to eq(2)
46
+ end
47
+
48
+ context "with longest option" do
49
+ subject { user.streak(:posts, longest: true) }
50
+
51
+ it "returns the longer streak" do
52
+ expect(subject).to eq(3)
53
+ end
54
+ end
55
+ end
56
+
57
+ context "user didn't post today, but has a streak before that" do
30
58
  let(:post_dates) { [3.days.ago, 2.day.ago, 1.day.ago] }
59
+ before { expect(post_dates.map(&:to_date)).to_not include(Date.current) }
31
60
 
32
61
  it "returns streak of zero" do
33
62
  expect(subject).to eq(0)
@@ -40,6 +69,14 @@ describe Streakable do
40
69
  expect(subject).to eq(post_dates.size)
41
70
  end
42
71
  end
72
+
73
+ context "but has three streaks, longest in the middle" do
74
+ let(:post_dates) { [3.days.ago, 4.days.ago, 6.days.ago, 7.days.ago, 8.days.ago, 10.days.ago, 11.days.ago] }
75
+
76
+ it "returns 0" do
77
+ expect(subject).to eq(0)
78
+ end
79
+ end
43
80
  end
44
81
 
45
82
  context "spanning two months" do
@@ -55,4 +92,89 @@ describe Streakable do
55
92
  end
56
93
  end
57
94
  end
95
+
96
+ describe "#streaks" do
97
+ subject { user.streaks(:posts) }
98
+
99
+ context "user has no posts" do
100
+ before { expect(user.posts).to be_empty }
101
+ it { is_expected.to eql([]) }
102
+ end
103
+
104
+ context "user posted on each of the last two days" do
105
+ let(:post_dates) { [1.day.ago, DateTime.current] }
106
+
107
+ it "returns the streak" do
108
+ expect(subject).to eq([[Date.current, 1.day.ago.to_date]])
109
+ end
110
+ end
111
+
112
+ context "user posted on each of the last three days" do
113
+ let(:post_dates) { [2.days.ago, 1.day.ago, DateTime.current] }
114
+
115
+ it "returns the streak" do
116
+ expect(subject).to eq([[Date.current, 1.day.ago.to_date, 2.days.ago.to_date]])
117
+ end
118
+ end
119
+
120
+ context "user only has a post today" do
121
+ let(:post_dates) { [DateTime.current] }
122
+ before do
123
+ expect(user.posts.size).to eql(1)
124
+ expect(user.posts.first.created_at.to_date).to eql(Date.current)
125
+ end
126
+
127
+ it "returns the streak of 1" do
128
+ expect(subject).to eq([[Date.current]])
129
+ end
130
+ end
131
+
132
+ context "user only has a post yesterday" do
133
+ let(:post_dates) { [DateTime.current.yesterday] }
134
+ before do
135
+ expect(user.posts.size).to eql(1)
136
+ expect(user.posts.first.created_at.to_date).to eql(Date.current.yesterday)
137
+ end
138
+
139
+ it "returns the streak of 1" do
140
+ expect(subject).to eq([[Date.current.yesterday]])
141
+ end
142
+ end
143
+
144
+ context "user has streak today, and a longer streak before that" do
145
+ let(:post_dates) { [DateTime.current, 1.day.ago, 3.days.ago, 4.days.ago, 5.days.ago] }
146
+
147
+ it "returns the streaks" do
148
+ expected = [[Date.current, 1.day.ago], [3.days.ago, 4.days.ago, 5.days.ago]].map do |x|
149
+ x.map(&:to_date)
150
+ end
151
+
152
+ expect(subject).to eq(expected)
153
+ end
154
+ end
155
+
156
+ context "user didn't post today, but has a streak of 3 before that" do
157
+ let(:post_dates) { [1.days.ago, 2.days.ago, 3.day.ago] }
158
+
159
+ it "returns the streak" do
160
+ expected = [[1.day.ago, 2.days.ago, 3.days.ago]].map do |x|
161
+ x.map(&:to_date)
162
+ end
163
+
164
+ expect(subject).to eq(expected)
165
+ end
166
+ end
167
+
168
+ context "user has three streaks, longest in the middle" do
169
+ let(:post_dates) { [3.days.ago, 4.days.ago, 6.days.ago, 7.days.ago, 8.days.ago, 10.days.ago, 11.days.ago] }
170
+
171
+ it "returns the streaks" do
172
+ expected = [[3.days.ago, 4.days.ago], [6.days.ago, 7.days.ago, 8.days.ago], [10.days.ago, 11.days.ago]].map do |x|
173
+ x.map(&:to_date)
174
+ end
175
+
176
+ expect(subject).to eq(expected)
177
+ end
178
+ end
179
+ end
58
180
  end
data/streakable.gemspec CHANGED
@@ -2,13 +2,15 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = 'streakable'
5
- spec.version = '0.1.0'
5
+ spec.version = '0.1.1'
6
6
  spec.authors = ['szTheory', 'Garrett Martin']
7
- spec.email = ['szTheory@github.com']
8
7
  spec.description = %q{Track consecutive day streaks on ActiveRecord models.}
9
- spec.summary = %q{Easily track consecutive day streaks on your Rails/ActiveRecord models.}
10
- spec.homepage = 'https://github.com/szTheory/streakable'
8
+ spec.summary = %q{Easily track consecutive day streaks on your Rails/ActiveRecord model associations for a given date column.}
9
+ spec.homepage = 'https://sztheory.github.io/streakable/'
11
10
  spec.license = 'MIT'
11
+ spec.metadata = {
12
+ "source_code_uri" => "https://github.com/szTheory/streakable",
13
+ }
12
14
 
13
15
  spec.files = `git ls-files`.split($/)
14
16
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: streakable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - szTheory
@@ -166,8 +166,7 @@ dependencies:
166
166
  - !ruby/object:Gem::Version
167
167
  version: 0.9.1
168
168
  description: Track consecutive day streaks on ActiveRecord models.
169
- email:
170
- - szTheory@github.com
169
+ email:
171
170
  executables: []
172
171
  extensions: []
173
172
  extra_rdoc_files: []
@@ -178,6 +177,7 @@ files:
178
177
  - LICENSE.txt
179
178
  - README.md
180
179
  - Rakefile
180
+ - _config.yml
181
181
  - lib/streakable.rb
182
182
  - lib/streakable/streak.rb
183
183
  - spec/spec_helper.rb
@@ -186,10 +186,11 @@ files:
186
186
  - spec/support/post.rb
187
187
  - spec/support/user.rb
188
188
  - streakable.gemspec
189
- homepage: https://github.com/szTheory/streakable
189
+ homepage: https://sztheory.github.io/streakable/
190
190
  licenses:
191
191
  - MIT
192
- metadata: {}
192
+ metadata:
193
+ source_code_uri: https://github.com/szTheory/streakable
193
194
  post_install_message:
194
195
  rdoc_options: []
195
196
  require_paths:
@@ -209,7 +210,8 @@ rubyforge_project:
209
210
  rubygems_version: 2.6.11
210
211
  signing_key:
211
212
  specification_version: 4
212
- summary: Easily track consecutive day streaks on your Rails/ActiveRecord models.
213
+ summary: Easily track consecutive day streaks on your Rails/ActiveRecord model associations
214
+ for a given date column.
213
215
  test_files:
214
216
  - spec/spec_helper.rb
215
217
  - spec/streakable_spec.rb