groupdate2 4.1.5 → 5.0.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 +58 -34
- data/CONTRIBUTING.md +1 -1
- data/LICENSE.txt +1 -1
- data/README.md +43 -9
- data/lib/groupdate/active_record.rb +1 -0
- data/lib/groupdate/enumerable.rb +9 -15
- data/lib/groupdate/magic.rb +66 -8
- data/lib/groupdate/query_methods.rb +3 -10
- data/lib/groupdate/relation.rb +7 -0
- data/lib/groupdate/relation_builder.rb +64 -48
- data/lib/groupdate/series_builder.rb +107 -62
- data/lib/groupdate/sql_server_group_clause.rb +3 -3
- data/lib/groupdate/version.rb +1 -1
- data/lib/groupdate2.rb +14 -4
- metadata +45 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7dfbcb0e43b31bb8e60084798e7b36b30fe7fb90
|
4
|
+
data.tar.gz: 0ac168fda29a6df9f1e28272ef80ee11c0029459
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b272dfa52234edaad18d44b78b129106b53a44ed79e577f593684428ba7b80972d78a48d99715ba52e0ecafab1e716b8cd318fd657293066ee91ad5f26f5c1cd
|
7
|
+
data.tar.gz: 53d31a3bd69f26d2cdb32617e8741c25a66991b29d1bca1544ec494991007b0ea5ed6b54040d0df49a32372958c647dd81975f54591c4da5856ee49bc7dd8456
|
data/CHANGELOG.md
CHANGED
@@ -1,31 +1,55 @@
|
|
1
|
-
##
|
1
|
+
## 5.0.0 (2020-02-18)
|
2
|
+
|
3
|
+
- Added support for `week_start` for SQLite
|
4
|
+
- Added support for full weekday names
|
5
|
+
- Made `day_start` behavior consistent between Active Record and enumerable
|
6
|
+
- Made `last` option extend to end of current period
|
7
|
+
- Raise error when `day_start` and `week_start` passed to unsupported methods
|
8
|
+
- The `day_start` option no longer applies to shorter periods
|
9
|
+
- Fixed `inconsistent time zone info` errors around DST with MySQL and PostgreSQL
|
10
|
+
- Improved performance of `format` option
|
11
|
+
- Removed deprecated positional arguments for time zone and range
|
12
|
+
- Dropped support for `mysql` gem (last release was 2013)
|
13
|
+
|
14
|
+
## 4.3.0 (2019-12-26)
|
15
|
+
|
16
|
+
- Fixed error with empty results in Ruby 2.7
|
17
|
+
- Fixed deprecation warnings in Ruby 2.7
|
18
|
+
- Deprecated positional arguments for time zone and range
|
19
|
+
|
20
|
+
## 4.2.0 (2019-10-28)
|
21
|
+
|
22
|
+
- Added `day_of_year`
|
23
|
+
- Dropped support for Rails 4.2
|
24
|
+
|
25
|
+
## 4.1.2 (2019-05-26)
|
2
26
|
|
3
27
|
- Fixed error with empty data and `current: false`
|
4
28
|
- Fixed error in time zone check for Rails < 5.2
|
5
29
|
- Prevent infinite loop with endless ranges
|
6
30
|
|
7
|
-
## 4.1.1
|
31
|
+
## 4.1.1 (2018-12-11)
|
8
32
|
|
9
33
|
- Made column resolution consistent with `group`
|
10
34
|
- Added support for `alias_attribute`
|
11
35
|
|
12
|
-
## 4.1.0
|
36
|
+
## 4.1.0 (2018-11-04)
|
13
37
|
|
14
38
|
- Many performance improvements
|
15
39
|
- Added check for consistent time zone info
|
16
40
|
- Fixed error message for invalid queries with MySQL and SQLite
|
17
41
|
- Fixed issue with enumerable methods ignoring nils
|
18
42
|
|
19
|
-
## 4.0.2
|
43
|
+
## 4.0.2 (2018-10-15)
|
20
44
|
|
21
45
|
- Make `current` option work without `last`
|
22
46
|
- 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
47
|
|
24
|
-
## 4.0.1
|
48
|
+
## 4.0.1 (2018-05-03)
|
25
49
|
|
26
50
|
- Fixed incorrect range with `last` option near time change
|
27
51
|
|
28
|
-
## 4.0.0
|
52
|
+
## 4.0.0 (2018-02-21)
|
29
53
|
|
30
54
|
- Custom calculation methods are supported by default - `groupdate_calculation_methods` is no longer needed
|
31
55
|
|
@@ -37,37 +61,37 @@ Breaking changes
|
|
37
61
|
- `week_start` now affects `day_of_week`
|
38
62
|
- Removed support for `reverse_order` (was never supported in Rails 5)
|
39
63
|
|
40
|
-
## 3.2.1
|
64
|
+
## 3.2.1 (2018-02-21)
|
41
65
|
|
42
66
|
- Added `minute_of_hour`
|
43
67
|
- Added support for `unscoped`
|
44
68
|
|
45
|
-
## 3.2.0
|
69
|
+
## 3.2.0 (2017-01-30)
|
46
70
|
|
47
71
|
- Added limited support for SQLite
|
48
72
|
|
49
|
-
## 3.1.1
|
73
|
+
## 3.1.1 (2016-10-25)
|
50
74
|
|
51
75
|
- Fixed `current: false`
|
52
76
|
- Fixed `last` with `group_by_quarter`
|
53
77
|
- Raise `ArgumentError` when `last` option is not supported
|
54
78
|
|
55
|
-
## 3.1.0
|
79
|
+
## 3.1.0 (2016-10-22)
|
56
80
|
|
57
81
|
- Better support for date columns with `time_zone: false`
|
58
82
|
- Better date range handling for `range` option
|
59
83
|
|
60
|
-
## 3.0.2
|
84
|
+
## 3.0.2 (2016-08-09)
|
61
85
|
|
62
86
|
- Fixed `group_by_period` with associations
|
63
87
|
- Fixed `week_start` option for enumerables
|
64
88
|
|
65
|
-
## 3.0.1
|
89
|
+
## 3.0.1 (2016-07-13)
|
66
90
|
|
67
91
|
- Added support for Redshift
|
68
92
|
- Fix for infinite loop in certain cases for Rails 5
|
69
93
|
|
70
|
-
## 3.0.0
|
94
|
+
## 3.0.0 (2016-05-30)
|
71
95
|
|
72
96
|
Breaking changes
|
73
97
|
|
@@ -75,16 +99,16 @@ Breaking changes
|
|
75
99
|
- Array and hash methods no longer return the entire series by default. Use `series: true` for the previous behavior.
|
76
100
|
- The `series: false` option now returns the correct types and order, and plays nicely with other options.
|
77
101
|
|
78
|
-
## 2.5.3
|
102
|
+
## 2.5.3 (2016-04-28)
|
79
103
|
|
80
104
|
- All tests green with `mysql` gem
|
81
105
|
- Added support for decimal day start
|
82
106
|
|
83
|
-
## 2.5.2
|
107
|
+
## 2.5.2 (2016-02-16)
|
84
108
|
|
85
109
|
- Added `dates` option to return dates for day, week, month, quarter, and year
|
86
110
|
|
87
|
-
## 2.5.1
|
111
|
+
## 2.5.1 (2016-02-03)
|
88
112
|
|
89
113
|
- Added `group_by_quarter`
|
90
114
|
- Added `default_value` option
|
@@ -92,13 +116,13 @@ Breaking changes
|
|
92
116
|
- Raise `ArgumentError` if no field specified
|
93
117
|
- Added support for ActiveRecord 5 beta
|
94
118
|
|
95
|
-
## 2.5.0
|
119
|
+
## 2.5.0 (2015-09-29)
|
96
120
|
|
97
121
|
- Added `group_by_period` method
|
98
122
|
- Added `current` option
|
99
123
|
- Raise `ArgumentError` if no block given to enumerable
|
100
124
|
|
101
|
-
## 2.4.0
|
125
|
+
## 2.4.0 (2014-12-28)
|
102
126
|
|
103
127
|
- Added localization
|
104
128
|
- Added `carry_forward` option
|
@@ -106,77 +130,77 @@ Breaking changes
|
|
106
130
|
- Fixed issue w/ Brasilia Summer Time
|
107
131
|
- Fixed issues w/ ActiveRecord 4.2
|
108
132
|
|
109
|
-
## 2.3.0
|
133
|
+
## 2.3.0 (2014-08-31)
|
110
134
|
|
111
135
|
- Raise error when ActiveRecord::Base.default_timezone is not `:utc`
|
112
136
|
- Added `day_of_month`
|
113
137
|
- Added `month_of_year`
|
114
138
|
- Do not quote column name
|
115
139
|
|
116
|
-
## 2.2.1
|
140
|
+
## 2.2.1 (2014-06-23)
|
117
141
|
|
118
142
|
- Fixed ActiveRecord 3 associations
|
119
143
|
|
120
|
-
## 2.2.0
|
144
|
+
## 2.2.0 (2014-06-22)
|
121
145
|
|
122
146
|
- Added support for arrays and hashes
|
123
147
|
|
124
|
-
## 2.1.1
|
148
|
+
## 2.1.1 (2014-05-17)
|
125
149
|
|
126
150
|
- Fixed format option with multiple groups
|
127
151
|
- Better error message if time zone support is missing for MySQL
|
128
152
|
|
129
|
-
## 2.1.0
|
153
|
+
## 2.1.0 (2014-03-16)
|
130
154
|
|
131
155
|
- Added last option
|
132
156
|
- Added format option
|
133
157
|
|
134
|
-
## 2.0.4
|
158
|
+
## 2.0.4 (2014-03-12)
|
135
159
|
|
136
160
|
- Added multiple groups
|
137
161
|
- Added order
|
138
162
|
- Subsequent methods no longer modify relation
|
139
163
|
|
140
|
-
## 2.0.3
|
164
|
+
## 2.0.3 (2014-03-11)
|
141
165
|
|
142
166
|
- Implemented respond_to?
|
143
167
|
|
144
|
-
## 2.0.2
|
168
|
+
## 2.0.2 (2014-03-11)
|
145
169
|
|
146
170
|
- where, joins, and includes no longer need to be before the group_by method
|
147
171
|
|
148
|
-
## 2.0.1
|
172
|
+
## 2.0.1 (2014-03-07)
|
149
173
|
|
150
174
|
- Use time zone instead of UTC for results
|
151
175
|
|
152
|
-
## 2.0.0
|
176
|
+
## 2.0.0 (2014-03-07)
|
153
177
|
|
154
178
|
- Returns entire series by default
|
155
179
|
- Added day_start option
|
156
180
|
- Better interface
|
157
181
|
|
158
|
-
## 1.0.5
|
182
|
+
## 1.0.5 (2014-03-06)
|
159
183
|
|
160
184
|
- Added global time_zone option
|
161
185
|
|
162
|
-
## 1.0.4
|
186
|
+
## 1.0.4 (2013-07-20)
|
163
187
|
|
164
188
|
- Added global week_start option
|
165
189
|
- Fixed bug with NULL values and series
|
166
190
|
|
167
|
-
## 1.0.3
|
191
|
+
## 1.0.3 (2013-07-05)
|
168
192
|
|
169
193
|
- Fixed deprecation warning when used with will_paginate
|
170
194
|
- Fixed bug with DateTime series
|
171
195
|
|
172
|
-
## 1.0.2
|
196
|
+
## 1.0.2 (2013-06-10)
|
173
197
|
|
174
198
|
- Added :start option for custom week start for group_by_week
|
175
199
|
|
176
|
-
## 1.0.1
|
200
|
+
## 1.0.1 (2013-06-03)
|
177
201
|
|
178
202
|
- Fixed series for Rails < 3.2 and MySQL
|
179
203
|
|
180
|
-
## 1.0.0
|
204
|
+
## 1.0.0 (2013-05-15)
|
181
205
|
|
182
206
|
- 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
@@ -1,16 +1,50 @@
|
|
1
1
|
# Groupdate2
|
2
|
-
groupdate2 is an enhanced version of the beautiful [Groupdate](https://github.com/ankane/groupdate) gem
|
3
|
-
|
2
|
+
groupdate2 is an enhanced version of the beautiful [Groupdate](https://github.com/ankane/groupdate) gem, it adds more features to Groupdate
|
3
|
+
- SQL Server 2016+ support
|
4
|
+
- series_label support
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
# Version
|
8
|
-
## 4.1.x
|
9
|
-
This version corresponds to groupdate 4.1.2 with SQL Server support.
|
10
|
-
|
11
|
-
## Installation
|
6
|
+
# Installation
|
12
7
|
Add this line to your application’s Gemfile:
|
13
8
|
```ruby
|
14
9
|
gem 'groupdate2'
|
15
10
|
```
|
16
11
|
|
12
|
+
# Features
|
13
|
+
## SQL Server 2016+
|
14
|
+
MS SQL Server (2016 and above) support by using (AT TIME ZONE).
|
15
|
+
|
16
|
+
## series_label, an option to include `group clause` in selected result
|
17
|
+
### User case
|
18
|
+
If you use groupdate not with ActiveRecord::Calculations, but manually selecting the calculation like:
|
19
|
+
```sql
|
20
|
+
sql = "COUNT(*) AS count, AVG(delivery.volume/truck.capacity) AS percentage, MIN(collected_at) AS collected_at"
|
21
|
+
```
|
22
|
+
Delivery.select(sql).group_by_day(:collected_at).to_a
|
23
|
+
|
24
|
+
It works well except the series label is not included in the result. I have to use MIN(collected_at) to have it, then convert it to the correct label in ruby code. It works but is cumbersome.
|
25
|
+
|
26
|
+
With this new option `series_label: collected_at_date`, it indicates the group clause should be included in the result and can be accessed by the method collected_at_date.
|
27
|
+
|
28
|
+
### Notes
|
29
|
+
- !!! This option does NOT work with ActiveRecord::Calculations.
|
30
|
+
- The series_label respects other Groupdate options like :locale, :dates and :format
|
31
|
+
|
32
|
+
[See more options](https://github.com/ankane/groupdate)
|
33
|
+
|
34
|
+
### An example
|
35
|
+
```ruby
|
36
|
+
users = User.select('COUNT(*) AS total, AVG(age) as average_age').group_by_month(:created_at, series_label: :created_at_month)
|
37
|
+
|
38
|
+
#The returned result would have attributes:
|
39
|
+
users.first.created_at_month
|
40
|
+
users.first.total
|
41
|
+
users.first.average_age
|
42
|
+
```
|
43
|
+
|
44
|
+
# Upgrading
|
45
|
+
## 5.0.x
|
46
|
+
- SQL server support
|
47
|
+
- series_label support
|
48
|
+
|
49
|
+
## 4.1.x
|
50
|
+
This version corresponds to groupdate 4.1.2 with SQL Server support.
|
data/lib/groupdate/enumerable.rb
CHANGED
@@ -1,31 +1,25 @@
|
|
1
1
|
module Enumerable
|
2
2
|
Groupdate::PERIODS.each do |period|
|
3
|
-
define_method :"group_by_#{period}" do |*args, &block|
|
3
|
+
define_method :"group_by_#{period}" do |*args, **options, &block|
|
4
4
|
if block
|
5
|
-
|
5
|
+
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0)" if args.any?
|
6
|
+
Groupdate::Magic::Enumerable.group_by(self, period, options, &block)
|
6
7
|
elsif respond_to?(:scoping)
|
7
|
-
scoping { @klass.
|
8
|
+
scoping { @klass.group_by_period(period, *args, **options, &block) }
|
8
9
|
else
|
9
10
|
raise ArgumentError, "no block given"
|
10
11
|
end
|
11
12
|
end
|
12
13
|
end
|
13
14
|
|
14
|
-
def group_by_period(*args, &block)
|
15
|
+
def group_by_period(period, *args, **options, &block)
|
15
16
|
if block || !respond_to?(:scoping)
|
16
|
-
|
17
|
-
options = args[1] || {}
|
17
|
+
raise ArgumentError, "wrong number of arguments (given #{args.size + 1}, expected 1)" if args.any?
|
18
18
|
|
19
|
-
|
20
|
-
#
|
21
|
-
permitted_periods = ((options.delete(:permit) || Groupdate::PERIODS).map(&:to_sym) & Groupdate::PERIODS).map(&:to_s)
|
22
|
-
if permitted_periods.include?(period.to_s)
|
23
|
-
send("group_by_#{period}", options, &block)
|
24
|
-
else
|
25
|
-
raise ArgumentError, "Unpermitted period"
|
26
|
-
end
|
19
|
+
Groupdate::Magic.validate_period(period, options.delete(:permit))
|
20
|
+
send("group_by_#{period}", **options, &block)
|
27
21
|
else
|
28
|
-
scoping { @klass.
|
22
|
+
scoping { @klass.group_by_period(period, *args, **options, &block) }
|
29
23
|
end
|
30
24
|
end
|
31
25
|
end
|
data/lib/groupdate/magic.rb
CHANGED
@@ -2,18 +2,42 @@ require "i18n"
|
|
2
2
|
|
3
3
|
module Groupdate
|
4
4
|
class Magic
|
5
|
+
DAYS = [:monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday]
|
6
|
+
|
5
7
|
attr_accessor :period, :options, :group_index
|
6
8
|
|
7
9
|
def initialize(period:, **options)
|
8
10
|
@period = period
|
9
11
|
@options = options
|
10
12
|
|
11
|
-
|
13
|
+
validate_keywords
|
14
|
+
validate_arguments
|
15
|
+
end
|
16
|
+
|
17
|
+
def validate_keywords
|
18
|
+
known_keywords = [:time_zone, :dates, :series, :format, :locale, :range, :reverse, :series_label]
|
19
|
+
|
20
|
+
if %i[week day_of_week].include?(period)
|
21
|
+
known_keywords << :week_start
|
22
|
+
end
|
23
|
+
|
24
|
+
if %i[day week month quarter year day_of_week hour_of_day day_of_month day_of_year month_of_year].include?(period)
|
25
|
+
known_keywords << :day_start
|
26
|
+
else
|
27
|
+
# prevent Groupdate.day_start from applying
|
28
|
+
@day_start = 0
|
29
|
+
end
|
30
|
+
|
31
|
+
unknown_keywords = options.keys - known_keywords
|
12
32
|
raise ArgumentError, "unknown keywords: #{unknown_keywords.join(", ")}" if unknown_keywords.any?
|
33
|
+
end
|
13
34
|
|
14
|
-
|
15
|
-
|
16
|
-
raise
|
35
|
+
def validate_arguments
|
36
|
+
# TODO better messages
|
37
|
+
raise ArgumentError, "Unrecognized time zone" unless time_zone
|
38
|
+
raise ArgumentError, "Unrecognized :week_start option" unless week_start
|
39
|
+
raise ArgumentError, "Cannot use endless range for :range option" if options[:range].is_a?(Range) && !options[:range].end
|
40
|
+
raise ArgumentError, ":day_start must be between 0 and 24" if (day_start / 3600) < 0 || (day_start / 3600) >= 24
|
17
41
|
end
|
18
42
|
|
19
43
|
def time_zone
|
@@ -25,13 +49,20 @@ module Groupdate
|
|
25
49
|
end
|
26
50
|
|
27
51
|
def week_start
|
28
|
-
@week_start ||=
|
52
|
+
@week_start ||= begin
|
53
|
+
v = (options[:week_start] || Groupdate.week_start).to_sym
|
54
|
+
DAYS.index(v) || [:mon, :tue, :wed, :thu, :fri, :sat, :sun].index(v)
|
55
|
+
end
|
29
56
|
end
|
30
57
|
|
31
58
|
def day_start
|
32
59
|
@day_start ||= ((options[:day_start] || Groupdate.day_start).to_f * 3600).round
|
33
60
|
end
|
34
61
|
|
62
|
+
def series_label
|
63
|
+
@series_label ||= (options[:series_label].present? ? options[:series_label] : nil)
|
64
|
+
end
|
65
|
+
|
35
66
|
def series_builder
|
36
67
|
@series_builder ||=
|
37
68
|
SeriesBuilder.new(
|
@@ -47,6 +78,11 @@ module Groupdate
|
|
47
78
|
series_builder.time_range
|
48
79
|
end
|
49
80
|
|
81
|
+
def self.validate_period(period, permit)
|
82
|
+
permitted_periods = ((permit || Groupdate::PERIODS).map(&:to_sym) & Groupdate::PERIODS).map(&:to_s)
|
83
|
+
raise ArgumentError, "Unpermitted period" unless permitted_periods.include?(period.to_s)
|
84
|
+
end
|
85
|
+
|
50
86
|
class Enumerable < Magic
|
51
87
|
def group_by(enum, &_block)
|
52
88
|
group = enum.group_by do |v|
|
@@ -85,10 +121,10 @@ module Groupdate
|
|
85
121
|
def cast_method
|
86
122
|
@cast_method ||= begin
|
87
123
|
case period
|
124
|
+
when :minute_of_hour, :hour_of_day, :day_of_month, :day_of_year, :month_of_year
|
125
|
+
lambda { |k| k.to_i }
|
88
126
|
when :day_of_week
|
89
127
|
lambda { |k| (k.to_i - 1 - week_start) % 7 }
|
90
|
-
when :hour_of_day, :day_of_month, :month_of_year, :minute_of_hour
|
91
|
-
lambda { |k| k.to_i }
|
92
128
|
else
|
93
129
|
utc = ActiveSupport::TimeZone["UTC"]
|
94
130
|
lambda { |k| (k.is_a?(String) || !k.respond_to?(:to_time) ? utc.parse(k.to_s) : k.to_time).in_time_zone(time_zone) }
|
@@ -130,6 +166,20 @@ module Groupdate
|
|
130
166
|
end
|
131
167
|
end
|
132
168
|
|
169
|
+
def perform_series_label(relation, result)
|
170
|
+
label = options[:series_label]
|
171
|
+
return result unless label.present?
|
172
|
+
|
173
|
+
result.map do |r|
|
174
|
+
r.send("#{label}=", cast_series_label(r.send(label)))
|
175
|
+
r
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def cast_series_label(original_label)
|
180
|
+
series_builder.format_series_label(cast_method.call(original_label))
|
181
|
+
end
|
182
|
+
|
133
183
|
def self.generate_relation(relation, field:, **options)
|
134
184
|
magic = Groupdate::Magic::Relation.new(**options)
|
135
185
|
|
@@ -142,7 +192,8 @@ module Groupdate
|
|
142
192
|
time_zone: magic.time_zone,
|
143
193
|
time_range: magic.time_range,
|
144
194
|
week_start: magic.week_start,
|
145
|
-
day_start: magic.day_start
|
195
|
+
day_start: magic.day_start,
|
196
|
+
series_label: magic.series_label
|
146
197
|
).generate
|
147
198
|
|
148
199
|
# add Groupdate info
|
@@ -159,6 +210,13 @@ module Groupdate
|
|
159
210
|
end
|
160
211
|
result
|
161
212
|
end
|
213
|
+
|
214
|
+
def self.process_series_label(relation, result)
|
215
|
+
relation.groupdate_values.reverse.each do |gv|
|
216
|
+
result = gv.perform_series_label(relation, result)
|
217
|
+
end
|
218
|
+
result
|
219
|
+
end
|
162
220
|
end
|
163
221
|
end
|
164
222
|
end
|