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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1a798d7e082e32adf30f0fd07348f8fec6369006
4
- data.tar.gz: 6fc8861fc493d524bc29b8be51ef030887d38dd4
3
+ metadata.gz: 7dfbcb0e43b31bb8e60084798e7b36b30fe7fb90
4
+ data.tar.gz: 0ac168fda29a6df9f1e28272ef80ee11c0029459
5
5
  SHA512:
6
- metadata.gz: 3c3459a9aa602d466dac261078dcd3dda23481e57b9cf1cf644699353f18e03af3924d0fda5f45887a6fad83d715ea7a0fc07f5772b30b0d4c34159fa0092bd3
7
- data.tar.gz: fe47222def4bec06307c1930aff1c81b2bc6dd2819e9413d4df7e766345834d80271fa899e06a3f6655b53337e3864c68a32b2351f99432760f46731ec7b6c7d
6
+ metadata.gz: b272dfa52234edaad18d44b78b129106b53a44ed79e577f593684428ba7b80972d78a48d99715ba52e0ecafab1e716b8cd318fd657293066ee91ad5f26f5c1cd
7
+ data.tar.gz: 53d31a3bd69f26d2cdb32617e8741c25a66991b29d1bca1544ec494991007b0ea5ed6b54040d0df49a32372958c647dd81975f54591c4da5856ee49bc7dd8456
@@ -1,31 +1,55 @@
1
- ## 4.1.2
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
@@ -57,7 +57,7 @@ brew services start mysql
57
57
 
58
58
  # create databases
59
59
  createdb groupdate_test
60
- mysql -u root -e "create database groupdate_test"
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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013-2019 Andrew Kane
1
+ Copyright (c) 2013-2020 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
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
- It adds MS SQL Server (2016 and above) support by using `(AT TIME ZONE)`.
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
- In JRuby, it relies on `activerecord-sqlserver-adapter` `jdbc-mode` branch, which is not ideal. The plan is using `activerecord-jdbcsqlserver-adapter` when JRuby 9.2+ is supported.
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.
@@ -4,3 +4,4 @@ require "groupdate/relation"
4
4
 
5
5
  ActiveRecord::Base.extend(Groupdate::QueryMethods)
6
6
  ActiveRecord::Relation.include(Groupdate::Relation)
7
+ ActiveRecord::Relation.prepend(Groupdate::RelationRecords)
@@ -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
- Groupdate::Magic::Enumerable.group_by(self, period, args[0] || {}, &block)
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.send(:"group_by_#{period}", *args, &block) }
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
- period = args[0]
17
- options = args[1] || {}
17
+ raise ArgumentError, "wrong number of arguments (given #{args.size + 1}, expected 1)" if args.any?
18
18
 
19
- options = options.dup
20
- # to_sym is unsafe on user input, so convert to strings
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.send(:group_by_period, *args, &block) }
22
+ scoping { @klass.group_by_period(period, *args, **options, &block) }
29
23
  end
30
24
  end
31
25
  end
@@ -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
- unknown_keywords = options.keys - [:day_start, :time_zone, :dates, :series, :week_start, :format, :locale, :range, :reverse]
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
- raise Groupdate::Error, "Unrecognized time zone" unless time_zone
15
- raise Groupdate::Error, "Unrecognized :week_start option" if period == :week && !week_start
16
- raise Groupdate::Error, "Cannot use endless range for :range option" if options[:range].is_a?(Range) && !options[:range].end
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 ||= [:mon, :tue, :wed, :thu, :fri, :sat, :sun].index((options[:week_start] || options[:start] || Groupdate.week_start).to_sym)
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