groupdate 2.0.4 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|