groupdate 4.1.2 → 6.0.1
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 +97 -34
- data/CONTRIBUTING.md +1 -1
- data/LICENSE.txt +1 -1
- data/README.md +41 -48
- data/lib/groupdate/adapters/base_adapter.rb +51 -0
- data/lib/groupdate/adapters/mysql_adapter.rb +63 -0
- data/lib/groupdate/adapters/postgresql_adapter.rb +46 -0
- data/lib/groupdate/adapters/sqlite_adapter.rb +51 -0
- data/lib/groupdate/enumerable.rb +9 -15
- data/lib/groupdate/magic.rb +111 -12
- data/lib/groupdate/query_methods.rb +3 -10
- data/lib/groupdate/relation.rb +3 -0
- data/lib/groupdate/series_builder.rb +112 -76
- data/lib/groupdate/version.rb +1 -1
- data/lib/groupdate.rb +29 -7
- metadata +15 -110
- data/lib/groupdate/relation_builder.rb +0 -186
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 12cfc48ae509ac20872022dd07429dd7bd8f24a5bfd81dcb16242f3519cb5385
|
|
4
|
+
data.tar.gz: 3079f700f956f141184fbc5bc8244ad90380804cea9f15fef51300985380d13e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 64b4e317731bc0032a914e4f07ca751ee276e41b7a30a2113bff9d9f2437196916c68e5f1f699c2e107861d0022b712b32315132cd0f530bb7caad5f41955e45
|
|
7
|
+
data.tar.gz: ea085f2e6985f4df40f591333935b88f8c59b2be87943f39241254dbbc8ac18cc1aaa2b54030880a804117ecdc0451a65463b07ea758d3fc93d99697a11a5711
|
data/CHANGELOG.md
CHANGED
|
@@ -1,31 +1,94 @@
|
|
|
1
|
-
##
|
|
1
|
+
## 6.0.1 (2022-01-16)
|
|
2
|
+
|
|
3
|
+
- Fixed incorrect results (error before 6.0) with `includes` with Active Record 6.1+
|
|
4
|
+
|
|
5
|
+
## 6.0.0 (2022-01-15)
|
|
6
|
+
|
|
7
|
+
- Raise `ActiveRecord::UnknownAttributeReference` for non-attribute arguments
|
|
8
|
+
- Raise `ArgumentError` for ranges with string bounds
|
|
9
|
+
- Added `n` option for Redshift
|
|
10
|
+
- Changed SQL to return dates instead of times for day, week, month, quarter, and year
|
|
11
|
+
- Removed `dates` option
|
|
12
|
+
- Dropped support for Ruby < 2.6 and Rails < 5.2
|
|
13
|
+
|
|
14
|
+
## 5.2.4 (2021-12-15)
|
|
15
|
+
|
|
16
|
+
- Simplified queries for Active Record 7 and MySQL
|
|
17
|
+
|
|
18
|
+
## 5.2.3 (2021-12-06)
|
|
19
|
+
|
|
20
|
+
- Fixed error and warnings with Active Record 7
|
|
21
|
+
|
|
22
|
+
## 5.2.2 (2021-02-08)
|
|
23
|
+
|
|
24
|
+
- Added support for `nil..nil` ranges in `range` option
|
|
25
|
+
|
|
26
|
+
## 5.2.1 (2020-09-09)
|
|
27
|
+
|
|
28
|
+
- Improved error message for invalid ranges
|
|
29
|
+
- Fixed bug with date string ranges
|
|
30
|
+
|
|
31
|
+
## 5.2.0 (2020-09-07)
|
|
32
|
+
|
|
33
|
+
- Added warning for non-attribute argument
|
|
34
|
+
- Added support for beginless and endless ranges in `range` option
|
|
35
|
+
|
|
36
|
+
## 5.1.0 (2020-07-30)
|
|
37
|
+
|
|
38
|
+
- Added `n` option to minute and second for custom durations
|
|
39
|
+
|
|
40
|
+
## 5.0.0 (2020-02-18)
|
|
41
|
+
|
|
42
|
+
- Added support for `week_start` for SQLite
|
|
43
|
+
- Added support for full weekday names
|
|
44
|
+
- Made `day_start` behavior consistent between Active Record and enumerable
|
|
45
|
+
- Made `last` option extend to end of current period
|
|
46
|
+
- Raise error when `day_start` and `week_start` passed to unsupported methods
|
|
47
|
+
- The `day_start` option no longer applies to shorter periods
|
|
48
|
+
- Fixed `inconsistent time zone info` errors around DST with MySQL and PostgreSQL
|
|
49
|
+
- Improved performance of `format` option
|
|
50
|
+
- Removed deprecated positional arguments for time zone and range
|
|
51
|
+
- Dropped support for `mysql` gem (last release was 2013)
|
|
52
|
+
|
|
53
|
+
## 4.3.0 (2019-12-26)
|
|
54
|
+
|
|
55
|
+
- Fixed error with empty results in Ruby 2.7
|
|
56
|
+
- Fixed deprecation warnings in Ruby 2.7
|
|
57
|
+
- Deprecated positional arguments for time zone and range
|
|
58
|
+
|
|
59
|
+
## 4.2.0 (2019-10-28)
|
|
60
|
+
|
|
61
|
+
- Added `day_of_year`
|
|
62
|
+
- Dropped support for Rails 4.2
|
|
63
|
+
|
|
64
|
+
## 4.1.2 (2019-05-26)
|
|
2
65
|
|
|
3
66
|
- Fixed error with empty data and `current: false`
|
|
4
67
|
- Fixed error in time zone check for Rails < 5.2
|
|
5
68
|
- Prevent infinite loop with endless ranges
|
|
6
69
|
|
|
7
|
-
## 4.1.1
|
|
70
|
+
## 4.1.1 (2018-12-11)
|
|
8
71
|
|
|
9
72
|
- Made column resolution consistent with `group`
|
|
10
73
|
- Added support for `alias_attribute`
|
|
11
74
|
|
|
12
|
-
## 4.1.0
|
|
75
|
+
## 4.1.0 (2018-11-04)
|
|
13
76
|
|
|
14
77
|
- Many performance improvements
|
|
15
78
|
- Added check for consistent time zone info
|
|
16
79
|
- Fixed error message for invalid queries with MySQL and SQLite
|
|
17
80
|
- Fixed issue with enumerable methods ignoring nils
|
|
18
81
|
|
|
19
|
-
## 4.0.2
|
|
82
|
+
## 4.0.2 (2018-10-15)
|
|
20
83
|
|
|
21
84
|
- Make `current` option work without `last`
|
|
22
85
|
- Fixed default value for `maximum`, `minimum`, and `average` (periods with no results now return `nil` instead of `0`, pass `default_value: 0` for previous behavior)
|
|
23
86
|
|
|
24
|
-
## 4.0.1
|
|
87
|
+
## 4.0.1 (2018-05-03)
|
|
25
88
|
|
|
26
89
|
- Fixed incorrect range with `last` option near time change
|
|
27
90
|
|
|
28
|
-
## 4.0.0
|
|
91
|
+
## 4.0.0 (2018-02-21)
|
|
29
92
|
|
|
30
93
|
- Custom calculation methods are supported by default - `groupdate_calculation_methods` is no longer needed
|
|
31
94
|
|
|
@@ -37,37 +100,37 @@ Breaking changes
|
|
|
37
100
|
- `week_start` now affects `day_of_week`
|
|
38
101
|
- Removed support for `reverse_order` (was never supported in Rails 5)
|
|
39
102
|
|
|
40
|
-
## 3.2.1
|
|
103
|
+
## 3.2.1 (2018-02-21)
|
|
41
104
|
|
|
42
105
|
- Added `minute_of_hour`
|
|
43
106
|
- Added support for `unscoped`
|
|
44
107
|
|
|
45
|
-
## 3.2.0
|
|
108
|
+
## 3.2.0 (2017-01-30)
|
|
46
109
|
|
|
47
110
|
- Added limited support for SQLite
|
|
48
111
|
|
|
49
|
-
## 3.1.1
|
|
112
|
+
## 3.1.1 (2016-10-25)
|
|
50
113
|
|
|
51
114
|
- Fixed `current: false`
|
|
52
115
|
- Fixed `last` with `group_by_quarter`
|
|
53
116
|
- Raise `ArgumentError` when `last` option is not supported
|
|
54
117
|
|
|
55
|
-
## 3.1.0
|
|
118
|
+
## 3.1.0 (2016-10-22)
|
|
56
119
|
|
|
57
120
|
- Better support for date columns with `time_zone: false`
|
|
58
121
|
- Better date range handling for `range` option
|
|
59
122
|
|
|
60
|
-
## 3.0.2
|
|
123
|
+
## 3.0.2 (2016-08-09)
|
|
61
124
|
|
|
62
125
|
- Fixed `group_by_period` with associations
|
|
63
126
|
- Fixed `week_start` option for enumerables
|
|
64
127
|
|
|
65
|
-
## 3.0.1
|
|
128
|
+
## 3.0.1 (2016-07-13)
|
|
66
129
|
|
|
67
130
|
- Added support for Redshift
|
|
68
131
|
- Fix for infinite loop in certain cases for Rails 5
|
|
69
132
|
|
|
70
|
-
## 3.0.0
|
|
133
|
+
## 3.0.0 (2016-05-30)
|
|
71
134
|
|
|
72
135
|
Breaking changes
|
|
73
136
|
|
|
@@ -75,16 +138,16 @@ Breaking changes
|
|
|
75
138
|
- Array and hash methods no longer return the entire series by default. Use `series: true` for the previous behavior.
|
|
76
139
|
- The `series: false` option now returns the correct types and order, and plays nicely with other options.
|
|
77
140
|
|
|
78
|
-
## 2.5.3
|
|
141
|
+
## 2.5.3 (2016-04-28)
|
|
79
142
|
|
|
80
143
|
- All tests green with `mysql` gem
|
|
81
144
|
- Added support for decimal day start
|
|
82
145
|
|
|
83
|
-
## 2.5.2
|
|
146
|
+
## 2.5.2 (2016-02-16)
|
|
84
147
|
|
|
85
148
|
- Added `dates` option to return dates for day, week, month, quarter, and year
|
|
86
149
|
|
|
87
|
-
## 2.5.1
|
|
150
|
+
## 2.5.1 (2016-02-03)
|
|
88
151
|
|
|
89
152
|
- Added `group_by_quarter`
|
|
90
153
|
- Added `default_value` option
|
|
@@ -92,13 +155,13 @@ Breaking changes
|
|
|
92
155
|
- Raise `ArgumentError` if no field specified
|
|
93
156
|
- Added support for ActiveRecord 5 beta
|
|
94
157
|
|
|
95
|
-
## 2.5.0
|
|
158
|
+
## 2.5.0 (2015-09-29)
|
|
96
159
|
|
|
97
160
|
- Added `group_by_period` method
|
|
98
161
|
- Added `current` option
|
|
99
162
|
- Raise `ArgumentError` if no block given to enumerable
|
|
100
163
|
|
|
101
|
-
## 2.4.0
|
|
164
|
+
## 2.4.0 (2014-12-28)
|
|
102
165
|
|
|
103
166
|
- Added localization
|
|
104
167
|
- Added `carry_forward` option
|
|
@@ -106,77 +169,77 @@ Breaking changes
|
|
|
106
169
|
- Fixed issue w/ Brasilia Summer Time
|
|
107
170
|
- Fixed issues w/ ActiveRecord 4.2
|
|
108
171
|
|
|
109
|
-
## 2.3.0
|
|
172
|
+
## 2.3.0 (2014-08-31)
|
|
110
173
|
|
|
111
174
|
- Raise error when ActiveRecord::Base.default_timezone is not `:utc`
|
|
112
175
|
- Added `day_of_month`
|
|
113
176
|
- Added `month_of_year`
|
|
114
177
|
- Do not quote column name
|
|
115
178
|
|
|
116
|
-
## 2.2.1
|
|
179
|
+
## 2.2.1 (2014-06-23)
|
|
117
180
|
|
|
118
181
|
- Fixed ActiveRecord 3 associations
|
|
119
182
|
|
|
120
|
-
## 2.2.0
|
|
183
|
+
## 2.2.0 (2014-06-22)
|
|
121
184
|
|
|
122
185
|
- Added support for arrays and hashes
|
|
123
186
|
|
|
124
|
-
## 2.1.1
|
|
187
|
+
## 2.1.1 (2014-05-17)
|
|
125
188
|
|
|
126
189
|
- Fixed format option with multiple groups
|
|
127
190
|
- Better error message if time zone support is missing for MySQL
|
|
128
191
|
|
|
129
|
-
## 2.1.0
|
|
192
|
+
## 2.1.0 (2014-03-16)
|
|
130
193
|
|
|
131
194
|
- Added last option
|
|
132
195
|
- Added format option
|
|
133
196
|
|
|
134
|
-
## 2.0.4
|
|
197
|
+
## 2.0.4 (2014-03-12)
|
|
135
198
|
|
|
136
199
|
- Added multiple groups
|
|
137
200
|
- Added order
|
|
138
201
|
- Subsequent methods no longer modify relation
|
|
139
202
|
|
|
140
|
-
## 2.0.3
|
|
203
|
+
## 2.0.3 (2014-03-11)
|
|
141
204
|
|
|
142
205
|
- Implemented respond_to?
|
|
143
206
|
|
|
144
|
-
## 2.0.2
|
|
207
|
+
## 2.0.2 (2014-03-11)
|
|
145
208
|
|
|
146
209
|
- where, joins, and includes no longer need to be before the group_by method
|
|
147
210
|
|
|
148
|
-
## 2.0.1
|
|
211
|
+
## 2.0.1 (2014-03-07)
|
|
149
212
|
|
|
150
213
|
- Use time zone instead of UTC for results
|
|
151
214
|
|
|
152
|
-
## 2.0.0
|
|
215
|
+
## 2.0.0 (2014-03-07)
|
|
153
216
|
|
|
154
217
|
- Returns entire series by default
|
|
155
218
|
- Added day_start option
|
|
156
219
|
- Better interface
|
|
157
220
|
|
|
158
|
-
## 1.0.5
|
|
221
|
+
## 1.0.5 (2014-03-06)
|
|
159
222
|
|
|
160
223
|
- Added global time_zone option
|
|
161
224
|
|
|
162
|
-
## 1.0.4
|
|
225
|
+
## 1.0.4 (2013-07-20)
|
|
163
226
|
|
|
164
227
|
- Added global week_start option
|
|
165
228
|
- Fixed bug with NULL values and series
|
|
166
229
|
|
|
167
|
-
## 1.0.3
|
|
230
|
+
## 1.0.3 (2013-07-05)
|
|
168
231
|
|
|
169
232
|
- Fixed deprecation warning when used with will_paginate
|
|
170
233
|
- Fixed bug with DateTime series
|
|
171
234
|
|
|
172
|
-
## 1.0.2
|
|
235
|
+
## 1.0.2 (2013-06-10)
|
|
173
236
|
|
|
174
237
|
- Added :start option for custom week start for group_by_week
|
|
175
238
|
|
|
176
|
-
## 1.0.1
|
|
239
|
+
## 1.0.1 (2013-06-03)
|
|
177
240
|
|
|
178
241
|
- Fixed series for Rails < 3.2 and MySQL
|
|
179
242
|
|
|
180
|
-
## 1.0.0
|
|
243
|
+
## 1.0.0 (2013-05-15)
|
|
181
244
|
|
|
182
245
|
- First major release
|
data/CONTRIBUTING.md
CHANGED
|
@@ -57,7 +57,7 @@ brew services start mysql
|
|
|
57
57
|
|
|
58
58
|
# create databases
|
|
59
59
|
createdb groupdate_test
|
|
60
|
-
|
|
60
|
+
mysqladmin create groupdate_test
|
|
61
61
|
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql
|
|
62
62
|
|
|
63
63
|
# clone the repo and run the tests
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -15,14 +15,14 @@ Supports PostgreSQL, MySQL, and Redshift, plus arrays and hashes (and limited su
|
|
|
15
15
|
|
|
16
16
|
:cupid: Goes hand in hand with [Chartkick](https://www.chartkick.com)
|
|
17
17
|
|
|
18
|
-
[](https://github.com/ankane/groupdate/actions)
|
|
19
19
|
|
|
20
20
|
## Installation
|
|
21
21
|
|
|
22
22
|
Add this line to your application’s Gemfile:
|
|
23
23
|
|
|
24
24
|
```ruby
|
|
25
|
-
gem
|
|
25
|
+
gem "groupdate"
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
For MySQL and SQLite, also follow [these instructions](#additional-instructions).
|
|
@@ -32,9 +32,9 @@ For MySQL and SQLite, also follow [these instructions](#additional-instructions)
|
|
|
32
32
|
```ruby
|
|
33
33
|
User.group_by_day(:created_at).count
|
|
34
34
|
# {
|
|
35
|
-
# Sat,
|
|
36
|
-
# Sun,
|
|
37
|
-
# Mon,
|
|
35
|
+
# Sat, 24 May 2020 => 50,
|
|
36
|
+
# Sun, 25 May 2020 => 100,
|
|
37
|
+
# Mon, 26 May 2020 => 34
|
|
38
38
|
# }
|
|
39
39
|
```
|
|
40
40
|
|
|
@@ -53,12 +53,14 @@ You can group by:
|
|
|
53
53
|
|
|
54
54
|
and
|
|
55
55
|
|
|
56
|
+
- minute_of_hour
|
|
56
57
|
- hour_of_day
|
|
57
58
|
- day_of_week (Sunday = 0, Monday = 1, etc)
|
|
58
59
|
- day_of_month
|
|
60
|
+
- day_of_year
|
|
59
61
|
- month_of_year
|
|
60
62
|
|
|
61
|
-
Use it anywhere you can use `group`. Works with `count`, `sum`, `minimum`, `maximum`, and `average`. For `median`, check out [ActiveMedian](https://github.com/ankane/active_median).
|
|
63
|
+
Use it anywhere you can use `group`. Works with `count`, `sum`, `minimum`, `maximum`, and `average`. For `median` and `percentile`, check out [ActiveMedian](https://github.com/ankane/active_median).
|
|
62
64
|
|
|
63
65
|
### Time Zones
|
|
64
66
|
|
|
@@ -73,9 +75,9 @@ or
|
|
|
73
75
|
```ruby
|
|
74
76
|
User.group_by_week(:created_at, time_zone: "Pacific Time (US & Canada)").count
|
|
75
77
|
# {
|
|
76
|
-
# Sun,
|
|
77
|
-
# Sun,
|
|
78
|
-
# Sun,
|
|
78
|
+
# Sun, 08 Mar 2020 => 70,
|
|
79
|
+
# Sun, 15 Mar 2020 => 54,
|
|
80
|
+
# Sun, 22 Mar 2020 => 80
|
|
79
81
|
# }
|
|
80
82
|
```
|
|
81
83
|
|
|
@@ -86,13 +88,13 @@ Time zone objects also work. To see a list of available time zones in Rails, run
|
|
|
86
88
|
Weeks start on Sunday by default. Change this with:
|
|
87
89
|
|
|
88
90
|
```ruby
|
|
89
|
-
Groupdate.week_start = :
|
|
91
|
+
Groupdate.week_start = :monday
|
|
90
92
|
```
|
|
91
93
|
|
|
92
94
|
or
|
|
93
95
|
|
|
94
96
|
```ruby
|
|
95
|
-
User.group_by_week(:created_at, week_start: :
|
|
97
|
+
User.group_by_week(:created_at, week_start: :monday).count
|
|
96
98
|
```
|
|
97
99
|
|
|
98
100
|
### Day Start
|
|
@@ -146,8 +148,8 @@ To get keys in a different format, use:
|
|
|
146
148
|
```ruby
|
|
147
149
|
User.group_by_month(:created_at, format: "%b %Y").count
|
|
148
150
|
# {
|
|
149
|
-
# "Jan
|
|
150
|
-
# "Feb
|
|
151
|
+
# "Jan 2020" => 10
|
|
152
|
+
# "Feb 2020" => 12
|
|
151
153
|
# }
|
|
152
154
|
```
|
|
153
155
|
|
|
@@ -192,6 +194,14 @@ User.group_by_period(params[:period], :created_at, permit: ["day", "week"]).coun
|
|
|
192
194
|
|
|
193
195
|
Raises an `ArgumentError` for unpermitted periods.
|
|
194
196
|
|
|
197
|
+
### Custom Duration
|
|
198
|
+
|
|
199
|
+
To group by a specific number of minutes or seconds, use:
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
User.group_by_minute(:created_at, n: 10).count # 10 minutes
|
|
203
|
+
```
|
|
204
|
+
|
|
195
205
|
### Date Columns
|
|
196
206
|
|
|
197
207
|
If grouping on date columns which don’t need time zone conversion, use:
|
|
@@ -200,17 +210,12 @@ If grouping on date columns which don’t need time zone conversion, use:
|
|
|
200
210
|
User.group_by_week(:created_on, time_zone: false).count
|
|
201
211
|
```
|
|
202
212
|
|
|
203
|
-
###
|
|
213
|
+
### Default Scopes
|
|
204
214
|
|
|
205
|
-
If
|
|
215
|
+
If you use Postgres and have a default scope that uses `order`, you may get a `column must appear in the GROUP BY clause` error (just like with Active Record’s `group` method). Remove the `order` scope with:
|
|
206
216
|
|
|
207
217
|
```ruby
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
# check against permitted columns
|
|
211
|
-
raise "Unpermitted column" unless ["column_a", "column_b"].include?(column)
|
|
212
|
-
|
|
213
|
-
User.group_by_day(column).count
|
|
218
|
+
User.unscope(:order).group_by_day(:count).count
|
|
214
219
|
```
|
|
215
220
|
|
|
216
221
|
## Arrays and Hashes
|
|
@@ -225,24 +230,28 @@ Supports the same options as above
|
|
|
225
230
|
users.group_by_day(time_zone: time_zone) { |u| u.created_at }
|
|
226
231
|
```
|
|
227
232
|
|
|
233
|
+
Get the entire series with:
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
users.group_by_day(series: true) { |u| u.created_at }
|
|
237
|
+
```
|
|
238
|
+
|
|
228
239
|
Count
|
|
229
240
|
|
|
230
241
|
```ruby
|
|
231
|
-
|
|
242
|
+
users.group_by_day { |u| u.created_at }.to_h { |k, v| [k, v.count] }
|
|
232
243
|
```
|
|
233
244
|
|
|
234
245
|
## Additional Instructions
|
|
235
246
|
|
|
236
247
|
### For MySQL
|
|
237
248
|
|
|
238
|
-
[Time zone support](https://dev.mysql.com/doc/refman/
|
|
249
|
+
[Time zone support](https://dev.mysql.com/doc/refman/8.0/en/time-zone-support.html) must be installed on the server.
|
|
239
250
|
|
|
240
251
|
```sh
|
|
241
252
|
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql
|
|
242
253
|
```
|
|
243
254
|
|
|
244
|
-
or copy and paste [these statements](https://gist.githubusercontent.com/ankane/1d6b0022173186accbf0/raw/time_zone_support.sql) into a SQL console.
|
|
245
|
-
|
|
246
255
|
You can confirm it worked with:
|
|
247
256
|
|
|
248
257
|
```sql
|
|
@@ -256,7 +265,7 @@ It should return the time instead of `NULL`.
|
|
|
256
265
|
Groupdate has limited support for SQLite.
|
|
257
266
|
|
|
258
267
|
- No time zone support
|
|
259
|
-
- No `day_start`
|
|
268
|
+
- No `day_start` option
|
|
260
269
|
- No `group_by_quarter` method
|
|
261
270
|
|
|
262
271
|
If your application’s time zone is set to something other than `Etc/UTC` (the default), create an initializer with:
|
|
@@ -267,36 +276,20 @@ Groupdate.time_zone = false
|
|
|
267
276
|
|
|
268
277
|
## Upgrading
|
|
269
278
|
|
|
270
|
-
###
|
|
271
|
-
|
|
272
|
-
Groupdate 4.0 brings a number of improvements. Here are a few to be aware of:
|
|
273
|
-
|
|
274
|
-
- `group_by` methods return an `ActiveRecord::Relation` instead of a `Groupdate::Series`
|
|
275
|
-
- Invalid options now throw an `ArgumentError`
|
|
276
|
-
- `week_start` now affects `day_of_week`
|
|
277
|
-
- Custom calculation methods are supported by default
|
|
278
|
-
|
|
279
|
-
### 3.0
|
|
279
|
+
### 6.0
|
|
280
280
|
|
|
281
|
-
Groupdate
|
|
281
|
+
Groupdate 6.0 protects against unsafe input by default. For non-attribute arguments, use:
|
|
282
282
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
### 2.0
|
|
288
|
-
|
|
289
|
-
Groupdate 2.0 brings a number of improvements. Here are two things to be aware of:
|
|
283
|
+
```ruby
|
|
284
|
+
User.group_by_day(Arel.sql(known_safe_value)).count
|
|
285
|
+
```
|
|
290
286
|
|
|
291
|
-
|
|
292
|
-
- `ActiveSupport::TimeWithZone` keys are now returned for every database adapter - adapters previously returned `Time` or `String` keys
|
|
287
|
+
Also, the `dates` option has been removed.
|
|
293
288
|
|
|
294
289
|
## History
|
|
295
290
|
|
|
296
291
|
View the [changelog](https://github.com/ankane/groupdate/blob/master/CHANGELOG.md)
|
|
297
292
|
|
|
298
|
-
Groupdate follows [Semantic Versioning](https://semver.org/)
|
|
299
|
-
|
|
300
293
|
## Contributing
|
|
301
294
|
|
|
302
295
|
Everyone is encouraged to help improve this project. Here are a few ways you can help:
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Groupdate
|
|
2
|
+
module Adapters
|
|
3
|
+
class BaseAdapter
|
|
4
|
+
attr_reader :period, :column, :day_start, :week_start, :n_seconds
|
|
5
|
+
|
|
6
|
+
def initialize(relation, column:, period:, time_zone:, time_range:, week_start:, day_start:, n_seconds:)
|
|
7
|
+
@relation = relation
|
|
8
|
+
@column = column
|
|
9
|
+
@period = period
|
|
10
|
+
@time_zone = time_zone
|
|
11
|
+
@time_range = time_range
|
|
12
|
+
@week_start = week_start
|
|
13
|
+
@day_start = day_start
|
|
14
|
+
@n_seconds = n_seconds
|
|
15
|
+
|
|
16
|
+
if ActiveRecord::VERSION::MAJOR >= 7
|
|
17
|
+
if ActiveRecord.default_timezone == :local
|
|
18
|
+
raise Groupdate::Error, "ActiveRecord.default_timezone must be :utc to use Groupdate"
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
if relation.default_timezone == :local
|
|
22
|
+
raise Groupdate::Error, "ActiveRecord::Base.default_timezone must be :utc to use Groupdate"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def generate
|
|
28
|
+
@relation.group(group_clause).where(*where_clause)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def where_clause
|
|
34
|
+
if @time_range.is_a?(Range)
|
|
35
|
+
if @time_range.end
|
|
36
|
+
op = @time_range.exclude_end? ? "<" : "<="
|
|
37
|
+
if @time_range.begin
|
|
38
|
+
["#{column} >= ? AND #{column} #{op} ?", @time_range.begin, @time_range.end]
|
|
39
|
+
else
|
|
40
|
+
["#{column} #{op} ?", @time_range.end]
|
|
41
|
+
end
|
|
42
|
+
else
|
|
43
|
+
["#{column} >= ?", @time_range.begin]
|
|
44
|
+
end
|
|
45
|
+
else
|
|
46
|
+
["#{column} IS NOT NULL"]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Groupdate
|
|
2
|
+
module Adapters
|
|
3
|
+
class MySQLAdapter < BaseAdapter
|
|
4
|
+
def group_clause
|
|
5
|
+
time_zone = @time_zone.tzinfo.name
|
|
6
|
+
day_start_column = "CONVERT_TZ(#{column}, '+00:00', ?) - INTERVAL ? second"
|
|
7
|
+
|
|
8
|
+
query =
|
|
9
|
+
case period
|
|
10
|
+
when :minute_of_hour
|
|
11
|
+
["MINUTE(#{day_start_column})", time_zone, day_start]
|
|
12
|
+
when :hour_of_day
|
|
13
|
+
["HOUR(#{day_start_column})", time_zone, day_start]
|
|
14
|
+
when :day_of_week
|
|
15
|
+
["DAYOFWEEK(#{day_start_column}) - 1", time_zone, day_start]
|
|
16
|
+
when :day_of_month
|
|
17
|
+
["DAYOFMONTH(#{day_start_column})", time_zone, day_start]
|
|
18
|
+
when :day_of_year
|
|
19
|
+
["DAYOFYEAR(#{day_start_column})", time_zone, day_start]
|
|
20
|
+
when :month_of_year
|
|
21
|
+
["MONTH(#{day_start_column})", time_zone, day_start]
|
|
22
|
+
when :week
|
|
23
|
+
["CAST(DATE_FORMAT(#{day_start_column} - INTERVAL ((? + DAYOFWEEK(#{day_start_column})) % 7) DAY, '%Y-%m-%d') AS DATE)", time_zone, day_start, 12 - week_start, time_zone, day_start]
|
|
24
|
+
when :quarter
|
|
25
|
+
["CAST(CONCAT(YEAR(#{day_start_column}), '-', LPAD(1 + 3 * (QUARTER(#{day_start_column}) - 1), 2, '00'), '-01') AS DATE)", time_zone, day_start, time_zone, day_start]
|
|
26
|
+
when :day, :month, :year
|
|
27
|
+
format =
|
|
28
|
+
case period
|
|
29
|
+
when :day
|
|
30
|
+
"%Y-%m-%d"
|
|
31
|
+
when :month
|
|
32
|
+
"%Y-%m-01"
|
|
33
|
+
else # year
|
|
34
|
+
"%Y-01-01"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
["CAST(DATE_FORMAT(#{day_start_column}, ?) AS DATE)", time_zone, day_start, format]
|
|
38
|
+
when :custom
|
|
39
|
+
["FROM_UNIXTIME((UNIX_TIMESTAMP(#{column}) DIV ?) * ?)", n_seconds, n_seconds]
|
|
40
|
+
else
|
|
41
|
+
format =
|
|
42
|
+
case period
|
|
43
|
+
when :second
|
|
44
|
+
"%Y-%m-%d %H:%i:%S"
|
|
45
|
+
when :minute
|
|
46
|
+
"%Y-%m-%d %H:%i:00"
|
|
47
|
+
else # hour
|
|
48
|
+
"%Y-%m-%d %H:00:00"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
["CONVERT_TZ(DATE_FORMAT(#{day_start_column}, ?) + INTERVAL ? second, ?, '+00:00')", time_zone, day_start, format, day_start, time_zone]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
clean_group_clause(@relation.send(:sanitize_sql_array, query))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def clean_group_clause(clause)
|
|
58
|
+
# zero quoted in Active Record 7+
|
|
59
|
+
clause.gsub(/ (\-|\+) INTERVAL 0 second/, "").gsub(/ (\-|\+) INTERVAL '0' second/, "")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Groupdate
|
|
2
|
+
module Adapters
|
|
3
|
+
class PostgreSQLAdapter < BaseAdapter
|
|
4
|
+
def group_clause
|
|
5
|
+
time_zone = @time_zone.tzinfo.name
|
|
6
|
+
day_start_column = "#{column}::timestamptz AT TIME ZONE ? - INTERVAL ?"
|
|
7
|
+
day_start_interval = "#{day_start} second"
|
|
8
|
+
|
|
9
|
+
query =
|
|
10
|
+
case period
|
|
11
|
+
when :minute_of_hour
|
|
12
|
+
["EXTRACT(MINUTE FROM #{day_start_column})::integer", time_zone, day_start_interval]
|
|
13
|
+
when :hour_of_day
|
|
14
|
+
["EXTRACT(HOUR FROM #{day_start_column})::integer", time_zone, day_start_interval]
|
|
15
|
+
when :day_of_week
|
|
16
|
+
["EXTRACT(DOW FROM #{day_start_column})::integer", time_zone, day_start_interval]
|
|
17
|
+
when :day_of_month
|
|
18
|
+
["EXTRACT(DAY FROM #{day_start_column})::integer", time_zone, day_start_interval]
|
|
19
|
+
when :day_of_year
|
|
20
|
+
["EXTRACT(DOY FROM #{day_start_column})::integer", time_zone, day_start_interval]
|
|
21
|
+
when :month_of_year
|
|
22
|
+
["EXTRACT(MONTH FROM #{day_start_column})::integer", time_zone, day_start_interval]
|
|
23
|
+
when :week
|
|
24
|
+
["(DATE_TRUNC('day', #{day_start_column} - INTERVAL '1 day' * ((? + EXTRACT(DOW FROM #{day_start_column})::integer) % 7)) + INTERVAL ?)::date", time_zone, day_start_interval, 13 - week_start, time_zone, day_start_interval, day_start_interval]
|
|
25
|
+
when :custom
|
|
26
|
+
if @relation.connection.adapter_name == "Redshift"
|
|
27
|
+
["TIMESTAMP 'epoch' + (FLOOR(EXTRACT(EPOCH FROM #{column}::timestamp) / ?) * ?) * INTERVAL '1 second'", n_seconds, n_seconds]
|
|
28
|
+
else
|
|
29
|
+
["TO_TIMESTAMP(FLOOR(EXTRACT(EPOCH FROM #{column}::timestamptz) / ?) * ?)", n_seconds, n_seconds]
|
|
30
|
+
end
|
|
31
|
+
when :day, :month, :quarter, :year
|
|
32
|
+
["DATE_TRUNC(?, #{day_start_column})::date", period, time_zone, day_start_interval]
|
|
33
|
+
else
|
|
34
|
+
# day start is always 0 for seconds, minute, hour
|
|
35
|
+
["DATE_TRUNC(?, #{day_start_column}) AT TIME ZONE ?", period, time_zone, day_start_interval, time_zone]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
clean_group_clause(@relation.send(:sanitize_sql_array, query))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def clean_group_clause(clause)
|
|
42
|
+
clause.gsub(/ (\-|\+) INTERVAL '0 second'/, "")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|