groupdate 2.0.4 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +16 -0
- data/lib/groupdate/scopes.rb +1 -1
- data/lib/groupdate/series.rb +88 -49
- data/lib/groupdate/version.rb +1 -1
- data/test/test_helper.rb +50 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 08308d10dc780963998b4ebcb906f802dd15f6a7
|
4
|
+
data.tar.gz: 4e2a7f3a352b94f22287e2e84e4e8830625914ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d302a57517c823e80b7192a42153bc4a594af23bd1647f6cd7f3eab77a57252882204e22d40e4a6a3a80253be4adce6577cb9fa723d52342f7f8d8cf4a725d77
|
7
|
+
data.tar.gz: 0fdeb289ba29919be219e0dc1cdaf040552ffdc2ffa1ed5c180178ba8c8f4bb8aa00d8ffb0862eb5030af8996144098b2d03380c96d0a5f97ce248362b148578
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -107,6 +107,12 @@ To get a specific time range, use:
|
|
107
107
|
User.group_by_day(:created_at, range: 2.weeks.ago.midnight..Time.now).count
|
108
108
|
```
|
109
109
|
|
110
|
+
To get the most recent time periods, use:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
User.group_by_week(:created_at, last: 8).count # last 8 weeks
|
114
|
+
```
|
115
|
+
|
110
116
|
### Order
|
111
117
|
|
112
118
|
You can order in descending order with:
|
@@ -121,6 +127,16 @@ or
|
|
121
127
|
User.group_by_day(:created_at).order("day desc").count
|
122
128
|
```
|
123
129
|
|
130
|
+
### Pretty Keys
|
131
|
+
|
132
|
+
To get keys in a different format, use:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
User.group_by_hour_of_day(:created_at, format: "%l %P").count.keys.first # 12 am
|
136
|
+
```
|
137
|
+
|
138
|
+
Takes a `String`, which is passed to [strftime](http://strfti.me/), or a `Proc`
|
139
|
+
|
124
140
|
## Installation
|
125
141
|
|
126
142
|
Add this line to your application’s Gemfile:
|
data/lib/groupdate/scopes.rb
CHANGED
@@ -76,7 +76,7 @@ module Groupdate
|
|
76
76
|
group = group(Groupdate::OrderHack.new(sanitize_sql_array(query), field, time_zone))
|
77
77
|
range = args[2] || options[:range] || true
|
78
78
|
unless options[:series] == false
|
79
|
-
Series.new(group, field, column, time_zone_object, range, week_start, day_start, group.group_values.size - 1)
|
79
|
+
Series.new(group, field, column, time_zone_object, range, week_start, day_start, group.group_values.size - 1, options)
|
80
80
|
else
|
81
81
|
group
|
82
82
|
end
|
data/lib/groupdate/series.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
module Groupdate
|
2
2
|
class Series
|
3
|
+
attr_accessor :relation
|
3
4
|
|
4
|
-
def initialize(relation, field, column, time_zone, time_range, week_start, day_start, group_index)
|
5
|
+
def initialize(relation, field, column, time_zone, time_range, week_start, day_start, group_index, options)
|
5
6
|
@relation = relation
|
6
7
|
@field = field
|
7
8
|
@column = column
|
@@ -10,20 +11,42 @@ module Groupdate
|
|
10
11
|
@week_start = week_start
|
11
12
|
@day_start = day_start
|
12
13
|
@group_index = group_index
|
14
|
+
@options = options
|
13
15
|
end
|
14
16
|
|
15
|
-
def
|
17
|
+
def perform(method, *args, &block)
|
16
18
|
utc = ActiveSupport::TimeZone["UTC"]
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
+
time_range = @time_range
|
21
|
+
if !time_range.is_a?(Range) and @options[:last]
|
22
|
+
step = 1.send(@field) if 1.respond_to?(@field)
|
23
|
+
if step
|
24
|
+
now = Time.now
|
25
|
+
time_range = round_time(now - (@options[:last].to_i - 1).send(@field))..now
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
relation =
|
30
|
+
if time_range.is_a?(Range)
|
31
|
+
# doesn't matter whether we include the end of a ... range - it will be excluded later
|
32
|
+
@relation.where("#{@column} >= ? AND #{@column} <= ?", time_range.first, time_range.last)
|
33
|
+
else
|
34
|
+
@relation.where("#{@column} IS NOT NULL")
|
35
|
+
end
|
36
|
+
|
37
|
+
# undo reverse since we do not want this to appear in the query
|
38
|
+
reverse = relation.reverse_order_value
|
39
|
+
if reverse
|
40
|
+
relation = relation.reverse_order
|
41
|
+
end
|
42
|
+
order = relation.order_values.first
|
20
43
|
if order.is_a?(String)
|
21
44
|
parts = order.split(" ")
|
22
45
|
reverse_order = (parts.size == 2 && parts[0] == @field && parts[1].to_s.downcase == "desc")
|
23
46
|
reverse = !reverse if reverse_order
|
24
47
|
end
|
25
48
|
|
26
|
-
multiple_groups =
|
49
|
+
multiple_groups = relation.group_values.size > 1
|
27
50
|
|
28
51
|
cast_method =
|
29
52
|
case @field
|
@@ -33,7 +56,7 @@ module Groupdate
|
|
33
56
|
lambda{|k| (k.is_a?(String) ? utc.parse(k) : k.to_time).in_time_zone(@time_zone) }
|
34
57
|
end
|
35
58
|
|
36
|
-
count = Hash[
|
59
|
+
count = Hash[ relation.send(method, *args, &block).map{|k, v| [multiple_groups ? k[0...@group_index] + [cast_method.call(k[@group_index])] + k[(@group_index + 1)..-1] : cast_method.call(k), v] } ]
|
37
60
|
|
38
61
|
series =
|
39
62
|
case @field
|
@@ -43,8 +66,8 @@ module Groupdate
|
|
43
66
|
0..23
|
44
67
|
else
|
45
68
|
time_range =
|
46
|
-
if
|
47
|
-
|
69
|
+
if time_range.is_a?(Range)
|
70
|
+
time_range
|
48
71
|
else
|
49
72
|
# use first and last values
|
50
73
|
sorted_keys =
|
@@ -57,30 +80,7 @@ module Groupdate
|
|
57
80
|
end
|
58
81
|
|
59
82
|
if time_range.first
|
60
|
-
|
61
|
-
time = time_range.first.to_time.in_time_zone(@time_zone) - @day_start.hours
|
62
|
-
starts_at =
|
63
|
-
case @field
|
64
|
-
when "second"
|
65
|
-
time.change(:usec => 0)
|
66
|
-
when "minute"
|
67
|
-
time.change(:sec => 0)
|
68
|
-
when "hour"
|
69
|
-
time.change(:min => 0)
|
70
|
-
when "day"
|
71
|
-
time.beginning_of_day
|
72
|
-
when "week"
|
73
|
-
# same logic as MySQL group
|
74
|
-
weekday = (time.wday - 1) % 7
|
75
|
-
(time - ((7 - @week_start + weekday) % 7).days).midnight
|
76
|
-
when "month"
|
77
|
-
time.beginning_of_month
|
78
|
-
else # year
|
79
|
-
time.beginning_of_year
|
80
|
-
end
|
81
|
-
|
82
|
-
starts_at += @day_start.hours
|
83
|
-
series = [starts_at]
|
83
|
+
series = [round_time(time_range.first)]
|
84
84
|
|
85
85
|
step = 1.send(@field)
|
86
86
|
|
@@ -107,31 +107,70 @@ module Groupdate
|
|
107
107
|
series = series.to_a.reverse
|
108
108
|
end
|
109
109
|
|
110
|
+
key_format =
|
111
|
+
if @options[:format]
|
112
|
+
if @options[:format].respond_to?(:call)
|
113
|
+
@options[:format]
|
114
|
+
else
|
115
|
+
sunday = @time_zone.parse("2014-03-02 00:00:00")
|
116
|
+
lambda do |key|
|
117
|
+
case @field
|
118
|
+
when "hour_of_day"
|
119
|
+
key = sunday + key.hours + @day_start.hours
|
120
|
+
when "day_of_week"
|
121
|
+
key = sunday + key.days
|
122
|
+
end
|
123
|
+
key.strftime(@options[:format].to_s)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
else
|
127
|
+
lambda{|k| k }
|
128
|
+
end
|
129
|
+
|
110
130
|
Hash[series.map do |k|
|
111
|
-
[k, count[k] || 0]
|
131
|
+
[key_format.call(k), count[k] || 0]
|
112
132
|
end]
|
113
133
|
end
|
114
134
|
|
115
|
-
def
|
116
|
-
|
117
|
-
if ActiveRecord::Calculations.method_defined?(method)
|
118
|
-
relation =
|
119
|
-
if @time_range.is_a?(Range)
|
120
|
-
# doesn't matter whether we include the end of a ... range - it will be excluded later
|
121
|
-
@relation.where("#{@column} >= ? AND #{@column} <= ?", @time_range.first, @time_range.last)
|
122
|
-
else
|
123
|
-
@relation.where("#{@column} IS NOT NULL")
|
124
|
-
end
|
135
|
+
def round_time(time)
|
136
|
+
time = time.to_time.in_time_zone(@time_zone) - @day_start.hours
|
125
137
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
138
|
+
time =
|
139
|
+
case @field
|
140
|
+
when "second"
|
141
|
+
time.change(:usec => 0)
|
142
|
+
when "minute"
|
143
|
+
time.change(:sec => 0)
|
144
|
+
when "hour"
|
145
|
+
time.change(:min => 0)
|
146
|
+
when "day"
|
147
|
+
time.beginning_of_day
|
148
|
+
when "week"
|
149
|
+
# same logic as MySQL group
|
150
|
+
weekday = (time.wday - 1) % 7
|
151
|
+
(time - ((7 - @week_start + weekday) % 7).days).midnight
|
152
|
+
when "month"
|
153
|
+
time.beginning_of_month
|
154
|
+
else # year
|
155
|
+
time.beginning_of_year
|
130
156
|
end
|
131
157
|
|
132
|
-
|
158
|
+
time + @day_start.hours
|
159
|
+
end
|
160
|
+
|
161
|
+
def clone
|
162
|
+
Groupdate::Series.new(@relation, @field, @column, @time_zone, @time_range, @week_start, @day_start, @group_index, @options)
|
163
|
+
end
|
164
|
+
|
165
|
+
# clone to prevent modifying original variables
|
166
|
+
def method_missing(method, *args, &block)
|
167
|
+
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/calculations.rb
|
168
|
+
if ActiveRecord::Calculations.method_defined?(method)
|
169
|
+
clone.perform(method, *args, &block)
|
133
170
|
elsif @relation.respond_to?(method)
|
134
|
-
|
171
|
+
series = clone
|
172
|
+
series.relation = @relation.send(method, *args, &block)
|
173
|
+
series
|
135
174
|
else
|
136
175
|
super
|
137
176
|
end
|
data/lib/groupdate/version.rb
CHANGED
data/test/test_helper.rb
CHANGED
@@ -549,8 +549,58 @@ module TestGroupdate
|
|
549
549
|
assert !User.group_by_day(:created_at).respond_to?(:no_such_method)
|
550
550
|
end
|
551
551
|
|
552
|
+
def test_last
|
553
|
+
create_user "2011-05-01 00:00:00 UTC"
|
554
|
+
create_user "2013-05-01 00:00:00 UTC"
|
555
|
+
expected = {
|
556
|
+
utc.parse("2012-01-01 00:00:00 UTC") => 0,
|
557
|
+
utc.parse("2013-01-01 00:00:00 UTC") => 1,
|
558
|
+
utc.parse("2014-01-01 00:00:00 UTC") => 0
|
559
|
+
}
|
560
|
+
assert_equal expected, User.group_by_year(:created_at, last: 3).count
|
561
|
+
end
|
562
|
+
|
563
|
+
def test_format_day
|
564
|
+
create_user "2014-03-01 00:00:00 UTC"
|
565
|
+
assert_format :day, "March 1, 2014", "%B %-e, %Y"
|
566
|
+
end
|
567
|
+
|
568
|
+
def test_format_month
|
569
|
+
create_user "2014-03-01 00:00:00 UTC"
|
570
|
+
assert_format :month, "March 2014", "%B %Y"
|
571
|
+
end
|
572
|
+
|
573
|
+
def test_format_year
|
574
|
+
create_user "2014-03-01 00:00:00 UTC"
|
575
|
+
assert_format :year, "2014", "%Y"
|
576
|
+
end
|
577
|
+
|
578
|
+
def test_format_hour_of_day
|
579
|
+
create_user "2014-03-01 00:00:00 UTC"
|
580
|
+
assert_format :hour_of_day, "12 am", "%-l %P"
|
581
|
+
end
|
582
|
+
|
583
|
+
def test_format_hour_of_day_day_start
|
584
|
+
create_user "2014-03-01 00:00:00 UTC"
|
585
|
+
assert_format :hour_of_day, "2 am", "%-l %P", day_start: 2
|
586
|
+
end
|
587
|
+
|
588
|
+
def test_format_day_of_week
|
589
|
+
create_user "2014-03-01 00:00:00 UTC"
|
590
|
+
assert_format :day_of_week, "Sun", "%a"
|
591
|
+
end
|
592
|
+
|
593
|
+
def test_format_day_of_week_week_start
|
594
|
+
create_user "2014-03-01 00:00:00 UTC"
|
595
|
+
assert_format :day_of_week, "Sun", "%a", week_start: :sat
|
596
|
+
end
|
597
|
+
|
552
598
|
# helpers
|
553
599
|
|
600
|
+
def assert_format(method, expected, format, options = {})
|
601
|
+
assert_equal expected, User.send(:"group_by_#{method}", :created_at, options.merge(format: format)).count.keys.first
|
602
|
+
end
|
603
|
+
|
554
604
|
def assert_result_time(method, expected, time_str, time_zone = false, options = {})
|
555
605
|
expected = {utc.parse(expected).in_time_zone(time_zone ? "Pacific Time (US & Canada)" : utc) => 1}
|
556
606
|
assert_equal expected, result(method, time_str, time_zone, options)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: groupdate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-03-
|
11
|
+
date: 2014-03-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|