groupdate2 4.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +182 -0
- data/CONTRIBUTING.md +75 -0
- data/LICENSE.txt +22 -0
- data/README.md +16 -0
- data/lib/groupdate/active_record.rb +6 -0
- data/lib/groupdate/enumerable.rb +31 -0
- data/lib/groupdate/magic.rb +164 -0
- data/lib/groupdate/query_methods.rb +25 -0
- data/lib/groupdate/relation.rb +16 -0
- data/lib/groupdate/relation_builder.rb +191 -0
- data/lib/groupdate/series_builder.rb +264 -0
- data/lib/groupdate/sql_server_group_clause.rb +92 -0
- data/lib/groupdate/version.rb +3 -0
- data/lib/groupdate/windows_zones.json +1 -0
- data/lib/groupdate2.rb +32 -0
- metadata +184 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1a798d7e082e32adf30f0fd07348f8fec6369006
|
4
|
+
data.tar.gz: 6fc8861fc493d524bc29b8be51ef030887d38dd4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3c3459a9aa602d466dac261078dcd3dda23481e57b9cf1cf644699353f18e03af3924d0fda5f45887a6fad83d715ea7a0fc07f5772b30b0d4c34159fa0092bd3
|
7
|
+
data.tar.gz: fe47222def4bec06307c1930aff1c81b2bc6dd2819e9413d4df7e766345834d80271fa899e06a3f6655b53337e3864c68a32b2351f99432760f46731ec7b6c7d
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
## 4.1.2
|
2
|
+
|
3
|
+
- Fixed error with empty data and `current: false`
|
4
|
+
- Fixed error in time zone check for Rails < 5.2
|
5
|
+
- Prevent infinite loop with endless ranges
|
6
|
+
|
7
|
+
## 4.1.1
|
8
|
+
|
9
|
+
- Made column resolution consistent with `group`
|
10
|
+
- Added support for `alias_attribute`
|
11
|
+
|
12
|
+
## 4.1.0
|
13
|
+
|
14
|
+
- Many performance improvements
|
15
|
+
- Added check for consistent time zone info
|
16
|
+
- Fixed error message for invalid queries with MySQL and SQLite
|
17
|
+
- Fixed issue with enumerable methods ignoring nils
|
18
|
+
|
19
|
+
## 4.0.2
|
20
|
+
|
21
|
+
- Make `current` option work without `last`
|
22
|
+
- 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
|
+
|
24
|
+
## 4.0.1
|
25
|
+
|
26
|
+
- Fixed incorrect range with `last` option near time change
|
27
|
+
|
28
|
+
## 4.0.0
|
29
|
+
|
30
|
+
- Custom calculation methods are supported by default - `groupdate_calculation_methods` is no longer needed
|
31
|
+
|
32
|
+
Breaking changes
|
33
|
+
|
34
|
+
- Dropped support for Rails < 4.2
|
35
|
+
- Invalid options now throw an `ArgumentError`
|
36
|
+
- `group_by` methods return an `ActiveRecord::Relation` instead of a `Groupdate::Series`
|
37
|
+
- `week_start` now affects `day_of_week`
|
38
|
+
- Removed support for `reverse_order` (was never supported in Rails 5)
|
39
|
+
|
40
|
+
## 3.2.1
|
41
|
+
|
42
|
+
- Added `minute_of_hour`
|
43
|
+
- Added support for `unscoped`
|
44
|
+
|
45
|
+
## 3.2.0
|
46
|
+
|
47
|
+
- Added limited support for SQLite
|
48
|
+
|
49
|
+
## 3.1.1
|
50
|
+
|
51
|
+
- Fixed `current: false`
|
52
|
+
- Fixed `last` with `group_by_quarter`
|
53
|
+
- Raise `ArgumentError` when `last` option is not supported
|
54
|
+
|
55
|
+
## 3.1.0
|
56
|
+
|
57
|
+
- Better support for date columns with `time_zone: false`
|
58
|
+
- Better date range handling for `range` option
|
59
|
+
|
60
|
+
## 3.0.2
|
61
|
+
|
62
|
+
- Fixed `group_by_period` with associations
|
63
|
+
- Fixed `week_start` option for enumerables
|
64
|
+
|
65
|
+
## 3.0.1
|
66
|
+
|
67
|
+
- Added support for Redshift
|
68
|
+
- Fix for infinite loop in certain cases for Rails 5
|
69
|
+
|
70
|
+
## 3.0.0
|
71
|
+
|
72
|
+
Breaking changes
|
73
|
+
|
74
|
+
- `Date` objects are now returned for day, week, month, quarter, and year by default. Use `dates: false` for the previous behavior, or change this globally with `Groupdate.dates = false`.
|
75
|
+
- Array and hash methods no longer return the entire series by default. Use `series: true` for the previous behavior.
|
76
|
+
- The `series: false` option now returns the correct types and order, and plays nicely with other options.
|
77
|
+
|
78
|
+
## 2.5.3
|
79
|
+
|
80
|
+
- All tests green with `mysql` gem
|
81
|
+
- Added support for decimal day start
|
82
|
+
|
83
|
+
## 2.5.2
|
84
|
+
|
85
|
+
- Added `dates` option to return dates for day, week, month, quarter, and year
|
86
|
+
|
87
|
+
## 2.5.1
|
88
|
+
|
89
|
+
- Added `group_by_quarter`
|
90
|
+
- Added `default_value` option
|
91
|
+
- Accept symbol for `format` option
|
92
|
+
- Raise `ArgumentError` if no field specified
|
93
|
+
- Added support for ActiveRecord 5 beta
|
94
|
+
|
95
|
+
## 2.5.0
|
96
|
+
|
97
|
+
- Added `group_by_period` method
|
98
|
+
- Added `current` option
|
99
|
+
- Raise `ArgumentError` if no block given to enumerable
|
100
|
+
|
101
|
+
## 2.4.0
|
102
|
+
|
103
|
+
- Added localization
|
104
|
+
- Added `carry_forward` option
|
105
|
+
- Added `series: false` option for arrays and hashes
|
106
|
+
- Fixed issue w/ Brasilia Summer Time
|
107
|
+
- Fixed issues w/ ActiveRecord 4.2
|
108
|
+
|
109
|
+
## 2.3.0
|
110
|
+
|
111
|
+
- Raise error when ActiveRecord::Base.default_timezone is not `:utc`
|
112
|
+
- Added `day_of_month`
|
113
|
+
- Added `month_of_year`
|
114
|
+
- Do not quote column name
|
115
|
+
|
116
|
+
## 2.2.1
|
117
|
+
|
118
|
+
- Fixed ActiveRecord 3 associations
|
119
|
+
|
120
|
+
## 2.2.0
|
121
|
+
|
122
|
+
- Added support for arrays and hashes
|
123
|
+
|
124
|
+
## 2.1.1
|
125
|
+
|
126
|
+
- Fixed format option with multiple groups
|
127
|
+
- Better error message if time zone support is missing for MySQL
|
128
|
+
|
129
|
+
## 2.1.0
|
130
|
+
|
131
|
+
- Added last option
|
132
|
+
- Added format option
|
133
|
+
|
134
|
+
## 2.0.4
|
135
|
+
|
136
|
+
- Added multiple groups
|
137
|
+
- Added order
|
138
|
+
- Subsequent methods no longer modify relation
|
139
|
+
|
140
|
+
## 2.0.3
|
141
|
+
|
142
|
+
- Implemented respond_to?
|
143
|
+
|
144
|
+
## 2.0.2
|
145
|
+
|
146
|
+
- where, joins, and includes no longer need to be before the group_by method
|
147
|
+
|
148
|
+
## 2.0.1
|
149
|
+
|
150
|
+
- Use time zone instead of UTC for results
|
151
|
+
|
152
|
+
## 2.0.0
|
153
|
+
|
154
|
+
- Returns entire series by default
|
155
|
+
- Added day_start option
|
156
|
+
- Better interface
|
157
|
+
|
158
|
+
## 1.0.5
|
159
|
+
|
160
|
+
- Added global time_zone option
|
161
|
+
|
162
|
+
## 1.0.4
|
163
|
+
|
164
|
+
- Added global week_start option
|
165
|
+
- Fixed bug with NULL values and series
|
166
|
+
|
167
|
+
## 1.0.3
|
168
|
+
|
169
|
+
- Fixed deprecation warning when used with will_paginate
|
170
|
+
- Fixed bug with DateTime series
|
171
|
+
|
172
|
+
## 1.0.2
|
173
|
+
|
174
|
+
- Added :start option for custom week start for group_by_week
|
175
|
+
|
176
|
+
## 1.0.1
|
177
|
+
|
178
|
+
- Fixed series for Rails < 3.2 and MySQL
|
179
|
+
|
180
|
+
## 1.0.0
|
181
|
+
|
182
|
+
- First major release
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
First, thanks for wanting to contribute. You’re awesome! :heart:
|
4
|
+
|
5
|
+
## Help
|
6
|
+
|
7
|
+
We’re not able to provide support through GitHub Issues. If you’re looking for help with your code, try posting on [Stack Overflow](https://stackoverflow.com/).
|
8
|
+
|
9
|
+
All features should be documented. If you don’t see a feature in the docs, assume it doesn’t exist.
|
10
|
+
|
11
|
+
## Bugs
|
12
|
+
|
13
|
+
Think you’ve discovered a bug?
|
14
|
+
|
15
|
+
1. Search existing issues to see if it’s been reported.
|
16
|
+
2. Try the `master` branch to make sure it hasn’t been fixed.
|
17
|
+
|
18
|
+
```rb
|
19
|
+
gem "groupdate", github: "ankane/groupdate"
|
20
|
+
```
|
21
|
+
|
22
|
+
If the above steps don’t help, create an issue. Include:
|
23
|
+
|
24
|
+
- Detailed steps to reproduce
|
25
|
+
- Complete backtraces for exceptions
|
26
|
+
|
27
|
+
## New Features
|
28
|
+
|
29
|
+
If you’d like to discuss a new feature, create an issue and start the title with `[Idea]`.
|
30
|
+
|
31
|
+
## Pull Requests
|
32
|
+
|
33
|
+
Fork the project and create a pull request. A few tips:
|
34
|
+
|
35
|
+
- Keep changes to a minimum. If you have multiple features or fixes, submit multiple pull requests.
|
36
|
+
- Follow the existing style. The code should read like it’s written by a single person.
|
37
|
+
- Add one or more tests if possible. Make sure existing tests pass with:
|
38
|
+
|
39
|
+
```sh
|
40
|
+
bundle exec rake test
|
41
|
+
```
|
42
|
+
|
43
|
+
Feel free to open an issue to get feedback on your idea before spending too much time on it.
|
44
|
+
|
45
|
+
## Dev Setup
|
46
|
+
|
47
|
+
On Mac:
|
48
|
+
|
49
|
+
```sh
|
50
|
+
# install and run PostgreSQL
|
51
|
+
brew install postgresql
|
52
|
+
brew services start postgresql
|
53
|
+
|
54
|
+
# install and run MySQL
|
55
|
+
brew install mysql
|
56
|
+
brew services start mysql
|
57
|
+
|
58
|
+
# create databases
|
59
|
+
createdb groupdate_test
|
60
|
+
mysql -u root -e "create database groupdate_test"
|
61
|
+
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql
|
62
|
+
|
63
|
+
# clone the repo and run the tests
|
64
|
+
git clone https://github.com/ankane/groupdate.git
|
65
|
+
cd groupdate
|
66
|
+
bundle install
|
67
|
+
bundle exec rake test
|
68
|
+
|
69
|
+
# run a single test file
|
70
|
+
ruby test/postgresql_test.rb
|
71
|
+
```
|
72
|
+
|
73
|
+
---
|
74
|
+
|
75
|
+
This contributing guide is released under [CCO](https://creativecommons.org/publicdomain/zero/1.0/) (public domain). Use it for your own project without attribution.
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013-2019 Andrew Kane
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,16 @@
|
|
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)`.
|
4
|
+
|
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
|
12
|
+
Add this line to your application’s Gemfile:
|
13
|
+
```ruby
|
14
|
+
gem 'groupdate2'
|
15
|
+
```
|
16
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Enumerable
|
2
|
+
Groupdate::PERIODS.each do |period|
|
3
|
+
define_method :"group_by_#{period}" do |*args, &block|
|
4
|
+
if block
|
5
|
+
Groupdate::Magic::Enumerable.group_by(self, period, args[0] || {}, &block)
|
6
|
+
elsif respond_to?(:scoping)
|
7
|
+
scoping { @klass.send(:"group_by_#{period}", *args, &block) }
|
8
|
+
else
|
9
|
+
raise ArgumentError, "no block given"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def group_by_period(*args, &block)
|
15
|
+
if block || !respond_to?(:scoping)
|
16
|
+
period = args[0]
|
17
|
+
options = args[1] || {}
|
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
|
27
|
+
else
|
28
|
+
scoping { @klass.send(:group_by_period, *args, &block) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require "i18n"
|
2
|
+
|
3
|
+
module Groupdate
|
4
|
+
class Magic
|
5
|
+
attr_accessor :period, :options, :group_index
|
6
|
+
|
7
|
+
def initialize(period:, **options)
|
8
|
+
@period = period
|
9
|
+
@options = options
|
10
|
+
|
11
|
+
unknown_keywords = options.keys - [:day_start, :time_zone, :dates, :series, :week_start, :format, :locale, :range, :reverse]
|
12
|
+
raise ArgumentError, "unknown keywords: #{unknown_keywords.join(", ")}" if unknown_keywords.any?
|
13
|
+
|
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
|
17
|
+
end
|
18
|
+
|
19
|
+
def time_zone
|
20
|
+
@time_zone ||= begin
|
21
|
+
time_zone = "Etc/UTC" if options[:time_zone] == false
|
22
|
+
time_zone ||= options[:time_zone] || Groupdate.time_zone || (Groupdate.time_zone == false && "Etc/UTC") || Time.zone || "Etc/UTC"
|
23
|
+
time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone[time_zone]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def week_start
|
28
|
+
@week_start ||= [:mon, :tue, :wed, :thu, :fri, :sat, :sun].index((options[:week_start] || options[:start] || Groupdate.week_start).to_sym)
|
29
|
+
end
|
30
|
+
|
31
|
+
def day_start
|
32
|
+
@day_start ||= ((options[:day_start] || Groupdate.day_start).to_f * 3600).round
|
33
|
+
end
|
34
|
+
|
35
|
+
def series_builder
|
36
|
+
@series_builder ||=
|
37
|
+
SeriesBuilder.new(
|
38
|
+
**options,
|
39
|
+
period: period,
|
40
|
+
time_zone: time_zone,
|
41
|
+
day_start: day_start,
|
42
|
+
week_start: week_start
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def time_range
|
47
|
+
series_builder.time_range
|
48
|
+
end
|
49
|
+
|
50
|
+
class Enumerable < Magic
|
51
|
+
def group_by(enum, &_block)
|
52
|
+
group = enum.group_by do |v|
|
53
|
+
v = yield(v)
|
54
|
+
raise ArgumentError, "Not a time" unless v.respond_to?(:to_time)
|
55
|
+
series_builder.round_time(v)
|
56
|
+
end
|
57
|
+
series_builder.generate(group, default_value: [], series_default: false)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.group_by(enum, period, options, &block)
|
61
|
+
Groupdate::Magic::Enumerable.new(period: period, **options).group_by(enum, &block)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class Relation < Magic
|
66
|
+
def initialize(**options)
|
67
|
+
super(**options.reject { |k, _| [:default_value, :carry_forward, :last, :current].include?(k) })
|
68
|
+
@options = options
|
69
|
+
end
|
70
|
+
|
71
|
+
def perform(relation, result, default_value:)
|
72
|
+
multiple_groups = relation.group_values.size > 1
|
73
|
+
|
74
|
+
check_nils(result, multiple_groups, relation)
|
75
|
+
result = cast_result(result, multiple_groups)
|
76
|
+
|
77
|
+
series_builder.generate(
|
78
|
+
result,
|
79
|
+
default_value: options.key?(:default_value) ? options[:default_value] : default_value,
|
80
|
+
multiple_groups: multiple_groups,
|
81
|
+
group_index: group_index
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
def cast_method
|
86
|
+
@cast_method ||= begin
|
87
|
+
case period
|
88
|
+
when :day_of_week
|
89
|
+
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
|
+
else
|
93
|
+
utc = ActiveSupport::TimeZone["UTC"]
|
94
|
+
lambda { |k| (k.is_a?(String) || !k.respond_to?(:to_time) ? utc.parse(k.to_s) : k.to_time).in_time_zone(time_zone) }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def cast_result(result, multiple_groups)
|
100
|
+
new_result = {}
|
101
|
+
result.each do |k, v|
|
102
|
+
if multiple_groups
|
103
|
+
k[group_index] = cast_method.call(k[group_index])
|
104
|
+
else
|
105
|
+
k = cast_method.call(k)
|
106
|
+
end
|
107
|
+
new_result[k] = v
|
108
|
+
end
|
109
|
+
new_result
|
110
|
+
end
|
111
|
+
|
112
|
+
def time_zone_support?(relation)
|
113
|
+
if relation.connection.adapter_name =~ /mysql/i
|
114
|
+
# need to call klass for Rails < 5.2
|
115
|
+
sql = relation.klass.send(:sanitize_sql_array, ["SELECT CONVERT_TZ(NOW(), '+00:00', ?)", time_zone.tzinfo.name])
|
116
|
+
!relation.connection.select_all(sql).first.values.first.nil?
|
117
|
+
else
|
118
|
+
true
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def check_nils(result, multiple_groups, relation)
|
123
|
+
has_nils = multiple_groups ? (result.keys.first && result.keys.first[group_index].nil?) : result.key?(nil)
|
124
|
+
if has_nils
|
125
|
+
if time_zone_support?(relation)
|
126
|
+
raise Groupdate::Error, "Invalid query - be sure to use a date or time column"
|
127
|
+
else
|
128
|
+
raise Groupdate::Error, "Database missing time zone support for #{time_zone.tzinfo.name} - see https://github.com/ankane/groupdate#for-mysql"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.generate_relation(relation, field:, **options)
|
134
|
+
magic = Groupdate::Magic::Relation.new(**options)
|
135
|
+
|
136
|
+
# generate ActiveRecord relation
|
137
|
+
relation =
|
138
|
+
RelationBuilder.new(
|
139
|
+
relation,
|
140
|
+
column: field,
|
141
|
+
period: magic.period,
|
142
|
+
time_zone: magic.time_zone,
|
143
|
+
time_range: magic.time_range,
|
144
|
+
week_start: magic.week_start,
|
145
|
+
day_start: magic.day_start
|
146
|
+
).generate
|
147
|
+
|
148
|
+
# add Groupdate info
|
149
|
+
magic.group_index = relation.group_values.size - 1
|
150
|
+
(relation.groupdate_values ||= []) << magic
|
151
|
+
|
152
|
+
relation
|
153
|
+
end
|
154
|
+
|
155
|
+
# allow any options to keep flexible for future
|
156
|
+
def self.process_result(relation, result, **options)
|
157
|
+
relation.groupdate_values.reverse.each do |gv|
|
158
|
+
result = gv.perform(relation, result, default_value: options[:default_value])
|
159
|
+
end
|
160
|
+
result
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|