groupdate 3.2.0 → 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: df818b929dceeddfccab948ceb591ad26c7334c8
4
- data.tar.gz: 370a704d7ff6a17fb82bb057d717141ed87bc69f
3
+ metadata.gz: f98770b6255920a055b3d7ab6c9bf36227580077
4
+ data.tar.gz: 4d7d1eb90c872e35ba89ad0eead165841f1e0506
5
5
  SHA512:
6
- metadata.gz: 7a77d1af0c530bfab9552ddff8f7149575c9821bcff772d33454b50c0050561d861a7eb5f220c5aa19200f80194f07c6a550c412be2ac76eedd88dbddb143faf
7
- data.tar.gz: 91d207a5493bcafe51e94a884809e37b8250e9c2e581bf26172a5d0810d32c64aa63f21aead7935d1096c4d27dcde4c8d49cc8de821cdae4c83634e6a168cf94
6
+ metadata.gz: 4aca35fb45ab76e77535404eb6123efc25bcdf4b2414b7255f8dd62e87e4b1d567eaf367807f93ac9ce18f5a571d752ea4d3dc2c47cc141413ebd49eeb656b31
7
+ data.tar.gz: 3f4ade3ccaab2acc62a413ca93ff618b1d89c2daa0c4d341b895b76db055dd6c0b208826c12696eb4c3f83900afcea69560f6c9bdf8d34c15e3c781264785e1b
data/.travis.yml CHANGED
@@ -1,12 +1,11 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.2.4
3
+ - 2.4.2
4
4
  - jruby-9.1.5.0
5
5
  gemfile:
6
6
  - Gemfile
7
- - test/gemfiles/activerecord32.gemfile
8
- - test/gemfiles/activerecord40.gemfile
9
- - test/gemfiles/activerecord41.gemfile
7
+ - test/gemfiles/activerecord52.gemfile
8
+ - test/gemfiles/activerecord50.gemfile
10
9
  - test/gemfiles/activerecord42.gemfile
11
10
  sudo: false
12
11
  script: RUBYOPT=W0 bundle exec rake test
@@ -15,11 +14,17 @@ before_install:
15
14
  - mysql -e 'create database groupdate_test;'
16
15
  - mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql
17
16
  - psql -c 'create database groupdate_test;' -U postgres
17
+ env:
18
+ - TRAVIS=t
18
19
  notifications:
19
20
  email:
20
21
  on_success: never
21
22
  on_failure: change
22
23
  matrix:
23
24
  allow_failures:
25
+ - rvm: 2.4.2
26
+ gemfile: test/gemfiles/activerecord52.gemfile
24
27
  - rvm: jruby-9.1.5.0
25
- gemfile: Gemfile
28
+ gemfile: test/gemfiles/activerecord52.gemfile
29
+ - rvm: jruby-9.1.5.0
30
+ gemfile: test/gemfiles/activerecord42.gemfile
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 3.2.1
2
+
3
+ - Added `minute_of_hour`
4
+ - Added support for `unscoped`
5
+
1
6
  ## 3.2.0
2
7
 
3
8
  - Added limited support for SQLite
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,67 @@
1
+ # Contributing
2
+
3
+ First, thanks for wanting to contribute. You’re awesome! :heart:
4
+
5
+ ## Questions
6
+
7
+ Use [Stack Overflow](https://stackoverflow.com/) with the tag `groupdate`.
8
+
9
+ ## Feature Requests
10
+
11
+ Create an issue. Start the title with `[Idea]`.
12
+
13
+ ## Issues
14
+
15
+ Think you’ve discovered an issue?
16
+
17
+ 1. Search existing issues to see if it’s been reported.
18
+ 2. Try the `master` branch to make sure it hasn’t been fixed.
19
+
20
+ ```rb
21
+ gem "groupdate", github: "ankane/groupdate"
22
+ ```
23
+
24
+ If the above steps don’t help, create an issue. Include the complete backtrace for exceptions.
25
+
26
+ ## Pull Requests
27
+
28
+ Fork the project and create a pull request. A few tips:
29
+
30
+ - Keep changes to a minimum. If you have multiple features or fixes, submit multiple pull requests.
31
+ - Follow the existing style. The code should read like it’s written by a single person.
32
+ - Add one or more tests if possible. Make sure existing tests pass with:
33
+
34
+ ```sh
35
+ bundle exec rake test
36
+ ```
37
+
38
+ Feel free to open an issue to get feedback on your idea before spending too much time on it.
39
+
40
+ ## Dev Setup
41
+
42
+ On Mac:
43
+
44
+ ```sh
45
+ # install and run PostgreSQL
46
+ brew install postgresql
47
+ brew services start postgresql
48
+
49
+ # install and run MySQL
50
+ brew install mysql
51
+ brew services start mysql
52
+
53
+ # create databases
54
+ createdb groupdate_test
55
+ mysql -u root -e "create database groupdate_test"
56
+ mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql
57
+
58
+ # clone the repo and run the tests
59
+ git clone https://github.com/ankane/groupdate.git
60
+ cd groupdate
61
+ bundle install
62
+ bundle exec rake test
63
+ ```
64
+
65
+ ---
66
+
67
+ 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/Gemfile CHANGED
@@ -3,4 +3,4 @@ source "https://rubygems.org"
3
3
  # Specify your gem's dependencies in groupdate.gemspec
4
4
  gemspec
5
5
 
6
- gem "activerecord", "~> 5.0.0"
6
+ gem "activerecord", "5.1.4" # "~> 5.1.0"
data/ISSUE_TEMPLATE.md ADDED
@@ -0,0 +1,7 @@
1
+ Hi,
2
+
3
+ Before creating an issue, please check out the Contributing Guide:
4
+
5
+ https://github.com/ankane/groupdate/blob/master/CONTRIBUTING.md
6
+
7
+ Thanks!
data/README.md CHANGED
@@ -11,16 +11,12 @@ The simplest way to group by:
11
11
 
12
12
  :cake: Get the entire series - **the other best part**
13
13
 
14
- Supports PostgreSQL, MySQL, and Redshift, plus arrays and hashes
14
+ Supports PostgreSQL, MySQL, and Redshift, plus arrays and hashes (and limited support for [SQLite](#for-sqlite))
15
15
 
16
- Limited support for [SQLite](#for-sqlite)
16
+ :cupid: Goes hand in hand with [Chartkick](https://www.chartkick.com)
17
17
 
18
18
  [![Build Status](https://travis-ci.org/ankane/groupdate.svg?branch=master)](https://travis-ci.org/ankane/groupdate)
19
19
 
20
- :cupid: Goes hand in hand with [Chartkick](http://ankane.github.io/chartkick/)
21
-
22
- **Groupdate 3.0 was just released!** See [instructions for upgrading](#30). If you use Chartkick with Groupdate, we recommend Chartkick 2.0 and above.
23
-
24
20
  ## Get Started
25
21
 
26
22
  ```ruby
@@ -212,6 +208,37 @@ Count
212
208
  Hash[ users.group_by_day { |u| u.created_at }.map { |k, v| [k, v.size] } ]
213
209
  ```
214
210
 
211
+ ## Custom Calculation Methods
212
+
213
+ Groupdate knows all of the calculations defined by `ActiveRecord` like `count`,
214
+ `sum`, or `average`. However you may have your own class level calculation
215
+ methods that you need to tell Groupdate about. All you have to do is define the
216
+ class method `groupdate_calculation_methods` returning an array of the method
217
+ names as symbols.
218
+
219
+ ```ruby
220
+ class User < ApplicationRecord
221
+ def self.groupdate_calculation_methods
222
+ [:total_sign_ins]
223
+ end
224
+
225
+ def self.total_sign_ins
226
+ all.sum(:sign_ins)
227
+ end
228
+ end
229
+ ```
230
+
231
+ Then you can use your custom calculation method:
232
+
233
+ ```ruby
234
+ User.group_by_week(:created_at).total_sign_ins
235
+ ```
236
+
237
+ Note that even if your method uses one of the calculations from `ActiveRecord`,
238
+ you'll still need to add it to the `groupdate_calculation_methods` array to have
239
+ it return the Hash of dates to values. Otherwise it will return a
240
+ `Groupdate::Series` object.
241
+
215
242
  ## Installation
216
243
 
217
244
  Add this line to your application’s Gemfile:
data/groupdate.gemspec CHANGED
@@ -8,7 +8,6 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Groupdate::VERSION
9
9
  spec.authors = ["Andrew Kane"]
10
10
  spec.email = ["andrew@chartkick.com"]
11
- spec.description = "The simplest way to group temporal data"
12
11
  spec.summary = "The simplest way to group temporal data"
13
12
  spec.homepage = "https://github.com/ankane/groupdate"
14
13
  spec.license = "MIT"
@@ -20,7 +19,7 @@ Gem::Specification.new do |spec|
20
19
 
21
20
  spec.add_dependency "activesupport", ">= 3"
22
21
 
23
- spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "bundler"
24
23
  spec.add_development_dependency "rake"
25
24
  spec.add_development_dependency "minitest"
26
25
  spec.add_development_dependency "activerecord"
@@ -30,8 +29,8 @@ Gem::Specification.new do |spec|
30
29
  spec.add_development_dependency "activerecord-jdbcmysql-adapter"
31
30
  spec.add_development_dependency "activerecord-jdbcsqlite3-adapter"
32
31
  else
33
- spec.add_development_dependency "pg"
34
- spec.add_development_dependency "mysql2", "~> 0.3.20"
32
+ spec.add_development_dependency "pg", "< 1"
33
+ spec.add_development_dependency "mysql2"
35
34
  spec.add_development_dependency "sqlite3"
36
35
  end
37
36
  end
data/lib/groupdate.rb CHANGED
@@ -6,7 +6,7 @@ require "groupdate/magic"
6
6
  module Groupdate
7
7
  class Error < RuntimeError; end
8
8
 
9
- PERIODS = [:second, :minute, :hour, :day, :week, :month, :quarter, :year, :day_of_week, :hour_of_day, :day_of_month, :month_of_year]
9
+ PERIODS = [:second, :minute, :hour, :day, :week, :month, :quarter, :year, :day_of_week, :hour_of_day, :minute_of_hour, :day_of_month, :month_of_year]
10
10
  # backwards compatibility for anyone who happened to use it
11
11
  FIELDS = PERIODS
12
12
  METHODS = PERIODS.map { |v| :"group_by_#{v}" } + [:group_by_period]
@@ -2,15 +2,15 @@ require "i18n"
2
2
 
3
3
  module Groupdate
4
4
  class Magic
5
- attr_accessor :field, :options
5
+ attr_accessor :period, :options
6
6
 
7
- def initialize(field, options)
8
- @field = field
7
+ def initialize(period, options)
8
+ @period = period
9
9
  @options = options
10
10
 
11
11
  raise Groupdate::Error, "Unrecognized time zone" unless time_zone
12
12
 
13
- raise Groupdate::Error, "Unrecognized :week_start option" if field == :week && !week_start
13
+ raise Groupdate::Error, "Unrecognized :week_start option" if period == :week && !week_start
14
14
  end
15
15
 
16
16
  def group_by(enum, &_block)
@@ -28,13 +28,15 @@ module Groupdate
28
28
  adapter_name = relation.connection.adapter_name
29
29
  query =
30
30
  case adapter_name
31
- when "MySQL", "Mysql2", "Mysql2Spatial"
32
- case field
31
+ when "MySQL", "Mysql2", "Mysql2Spatial", 'Mysql2Rgeo'
32
+ case period
33
33
  when :day_of_week # Sunday = 0, Monday = 1, etc
34
34
  # use CONCAT for consistent return type (String)
35
35
  ["DAYOFWEEK(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?)) - 1", time_zone]
36
36
  when :hour_of_day
37
37
  ["(EXTRACT(HOUR from CONVERT_TZ(#{column}, '+00:00', ?)) + 24 - #{day_start / 3600}) % 24", time_zone]
38
+ when :minute_of_hour
39
+ ["(EXTRACT(MINUTE from CONVERT_TZ(#{column}, '+00:00', ?)))", time_zone]
38
40
  when :day_of_month
39
41
  ["DAYOFMONTH(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?))", time_zone]
40
42
  when :month_of_year
@@ -45,7 +47,7 @@ module Groupdate
45
47
  ["DATE_ADD(CONVERT_TZ(DATE_FORMAT(DATE(CONCAT(EXTRACT(YEAR FROM CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?)), '-', LPAD(1 + 3 * (QUARTER(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?)) - 1), 2, '00'), '-01')), '%Y-%m-%d %H:%i:%S'), ?, '+00:00'), INTERVAL #{day_start} second)", time_zone, time_zone, time_zone]
46
48
  else
47
49
  format =
48
- case field
50
+ case period
49
51
  when :second
50
52
  "%Y-%m-%d %H:%i:%S"
51
53
  when :minute
@@ -63,62 +65,68 @@ module Groupdate
63
65
  ["DATE_ADD(CONVERT_TZ(DATE_FORMAT(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?), '#{format}'), ?, '+00:00'), INTERVAL #{day_start} second)", time_zone, time_zone]
64
66
  end
65
67
  when "PostgreSQL", "PostGIS"
66
- case field
68
+ case period
67
69
  when :day_of_week
68
70
  ["EXTRACT(DOW from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
69
71
  when :hour_of_day
70
72
  ["EXTRACT(HOUR from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
73
+ when :minute_of_hour
74
+ ["EXTRACT(MINUTE from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
71
75
  when :day_of_month
72
76
  ["EXTRACT(DAY from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
73
77
  when :month_of_year
74
78
  ["EXTRACT(MONTH from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
75
79
  when :week # start on Sunday, not PostgreSQL default Monday
76
- ["(DATE_TRUNC('#{field}', (#{column}::timestamptz - INTERVAL '#{week_start} day' - INTERVAL '#{day_start} second') AT TIME ZONE ?) + INTERVAL '#{week_start} day' + INTERVAL '#{day_start} second') AT TIME ZONE ?", time_zone, time_zone]
80
+ ["(DATE_TRUNC('#{period}', (#{column}::timestamptz - INTERVAL '#{week_start} day' - INTERVAL '#{day_start} second') AT TIME ZONE ?) + INTERVAL '#{week_start} day' + INTERVAL '#{day_start} second') AT TIME ZONE ?", time_zone, time_zone]
77
81
  else
78
- ["(DATE_TRUNC('#{field}', (#{column}::timestamptz - INTERVAL '#{day_start} second') AT TIME ZONE ?) + INTERVAL '#{day_start} second') AT TIME ZONE ?", time_zone, time_zone]
82
+ ["(DATE_TRUNC('#{period}', (#{column}::timestamptz - INTERVAL '#{day_start} second') AT TIME ZONE ?) + INTERVAL '#{day_start} second') AT TIME ZONE ?", time_zone, time_zone]
79
83
  end
80
84
  when "SQLite"
81
85
  raise Groupdate::Error, "Time zones not supported for SQLite" unless self.time_zone.utc_offset.zero?
82
86
  raise Groupdate::Error, "day_start not supported for SQLite" unless day_start.zero?
83
87
  raise Groupdate::Error, "week_start not supported for SQLite" unless week_start == 6
84
88
 
85
- if field == :week
89
+ if period == :week
86
90
  ["strftime('%%Y-%%m-%%d 00:00:00 UTC', #{column}, '-6 days', 'weekday 0')"]
87
91
  else
88
92
  format =
89
- case field
90
- when :hour_of_day
91
- "%H"
92
- when :day_of_week
93
- "%w"
94
- when :day_of_month
95
- "%d"
96
- when :month_of_year
97
- "%m"
98
- when :second
99
- "%Y-%m-%d %H:%M:%S UTC"
100
- when :minute
101
- "%Y-%m-%d %H:%M:00 UTC"
102
- when :hour
103
- "%Y-%m-%d %H:00:00 UTC"
104
- when :day
105
- "%Y-%m-%d 00:00:00 UTC"
106
- when :month
107
- "%Y-%m-01 00:00:00 UTC"
108
- when :quarter
109
- raise Groupdate::Error, "Quarter not supported for SQLite"
110
- else # year
111
- "%Y-01-01 00:00:00 UTC"
112
- end
93
+ case period
94
+ when :hour_of_day
95
+ "%H"
96
+ when :minute_of_hour
97
+ "%M"
98
+ when :day_of_week
99
+ "%w"
100
+ when :day_of_month
101
+ "%d"
102
+ when :month_of_year
103
+ "%m"
104
+ when :second
105
+ "%Y-%m-%d %H:%M:%S UTC"
106
+ when :minute
107
+ "%Y-%m-%d %H:%M:00 UTC"
108
+ when :hour
109
+ "%Y-%m-%d %H:00:00 UTC"
110
+ when :day
111
+ "%Y-%m-%d 00:00:00 UTC"
112
+ when :month
113
+ "%Y-%m-01 00:00:00 UTC"
114
+ when :quarter
115
+ raise Groupdate::Error, "Quarter not supported for SQLite"
116
+ else # year
117
+ "%Y-01-01 00:00:00 UTC"
118
+ end
113
119
 
114
120
  ["strftime('#{format.gsub(/%/, '%%')}', #{column})"]
115
121
  end
116
122
  when "Redshift"
117
- case field
123
+ case period
118
124
  when :day_of_week # Sunday = 0, Monday = 1, etc.
119
125
  ["EXTRACT(DOW from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
120
126
  when :hour_of_day
121
127
  ["EXTRACT(HOUR from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
128
+ when :minute_of_hour
129
+ ["EXTRACT(MINUTE from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
122
130
  when :day_of_month
123
131
  ["EXTRACT(DAY from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
124
132
  when :month_of_year
@@ -128,19 +136,19 @@ module Groupdate
128
136
  # always says it is in UTC time, so we must convert
129
137
  # back to UTC to play properly with the rest of Groupdate.
130
138
  #
131
- ["CONVERT_TIMEZONE(?, 'Etc/UTC', DATE_TRUNC(?, CONVERT_TIMEZONE(?, #{column}) - INTERVAL '#{week_start} day' - INTERVAL '#{day_start} second'))::timestamp + INTERVAL '#{week_start} day' + INTERVAL '#{day_start} second'", time_zone, field, time_zone]
139
+ ["CONVERT_TIMEZONE(?, 'Etc/UTC', DATE_TRUNC(?, CONVERT_TIMEZONE(?, #{column}) - INTERVAL '#{week_start} day' - INTERVAL '#{day_start} second'))::timestamp + INTERVAL '#{week_start} day' + INTERVAL '#{day_start} second'", time_zone, period, time_zone]
132
140
  else
133
- ["CONVERT_TIMEZONE(?, 'Etc/UTC', DATE_TRUNC(?, CONVERT_TIMEZONE(?, #{column}) - INTERVAL '#{day_start} second'))::timestamp + INTERVAL '#{day_start} second'", time_zone, field, time_zone]
141
+ ["CONVERT_TIMEZONE(?, 'Etc/UTC', DATE_TRUNC(?, CONVERT_TIMEZONE(?, #{column}) - INTERVAL '#{day_start} second'))::timestamp + INTERVAL '#{day_start} second'", time_zone, period, time_zone]
134
142
  end
135
143
  else
136
144
  raise Groupdate::Error, "Connection adapter not supported: #{adapter_name}"
137
145
  end
138
146
 
139
- if adapter_name == "MySQL" && field == :week
147
+ if adapter_name == "MySQL" && period == :week
140
148
  query[0] = "CAST(#{query[0]} AS DATETIME)"
141
149
  end
142
150
 
143
- group = relation.group(Groupdate::OrderHack.new(relation.send(:sanitize_sql_array, query), field, time_zone))
151
+ group = relation.group(Groupdate::OrderHack.new(relation.send(:sanitize_sql_array, query), period, time_zone))
144
152
  relation =
145
153
  if time_range.is_a?(Range)
146
154
  # doesn't matter whether we include the end of a ... range - it will be excluded later
@@ -162,7 +170,7 @@ module Groupdate
162
170
  order = relation.order_values.first
163
171
  if order.is_a?(String)
164
172
  parts = order.split(" ")
165
- reverse_order = (parts.size == 2 && (parts[0].to_sym == field || (activerecord42? && parts[0] == "#{relation.quoted_table_name}.#{relation.quoted_primary_key}")) && parts[1].to_s.downcase == "desc")
173
+ reverse_order = (parts.size == 2 && (parts[0].to_sym == period || (activerecord42? && parts[0] == "#{relation.quoted_table_name}.#{relation.quoted_primary_key}")) && parts[1].to_s.downcase == "desc")
166
174
  if reverse_order
167
175
  reverse = !reverse
168
176
  relation = relation.reorder(relation.order_values[1..-1])
@@ -172,8 +180,8 @@ module Groupdate
172
180
  multiple_groups = relation.group_values.size > 1
173
181
 
174
182
  cast_method =
175
- case field
176
- when :day_of_week, :hour_of_day, :day_of_month, :month_of_year
183
+ case period
184
+ when :day_of_week, :hour_of_day, :day_of_month, :month_of_year, :minute_of_hour
177
185
  lambda { |k| k.to_i }
178
186
  else
179
187
  utc = ActiveSupport::TimeZone["UTC"]
@@ -181,13 +189,18 @@ module Groupdate
181
189
  end
182
190
 
183
191
  result = relation.send(method, *args, &block)
184
- missing_time_zone_support = multiple_groups ? (result.keys.first && result.keys.first[@group_index].nil?) : result.key?(nil)
185
- if missing_time_zone_support
186
- raise Groupdate::Error, "Be sure to install time zone support - https://github.com/ankane/groupdate#for-mysql"
187
- end
188
- result = Hash[result.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] }]
192
+ if result.is_a?(Hash)
193
+ missing_time_zone_support = multiple_groups ? (result.keys.first && result.keys.first[@group_index].nil?) : result.key?(nil)
194
+ if missing_time_zone_support
195
+ raise Groupdate::Error, "Be sure to install time zone support - https://github.com/ankane/groupdate#for-mysql"
196
+ end
197
+ result = Hash[result.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] }]
189
198
 
190
- series(result, (options.key?(:default_value) ? options[:default_value] : 0), multiple_groups, reverse)
199
+ series(result, (options.key?(:default_value) ? options[:default_value] : 0), multiple_groups, reverse)
200
+ else
201
+ # for ActiveRecord::Calculations methods that don't call calculate, like pluck
202
+ result
203
+ end
191
204
  end
192
205
 
193
206
  protected
@@ -218,12 +231,12 @@ module Groupdate
218
231
  last += 1.day unless time_range.exclude_end?
219
232
  time_range = Range.new(time_zone.parse(time_range.first.to_s), last, true)
220
233
  elsif !time_range && options[:last]
221
- if field == :quarter
234
+ if period == :quarter
222
235
  step = 3.months
223
- elsif 1.respond_to?(field)
224
- step = 1.send(field)
236
+ elsif 1.respond_to?(period)
237
+ step = 1.send(period)
225
238
  else
226
- raise ArgumentError, "Cannot use last option with #{field}"
239
+ raise ArgumentError, "Cannot use last option with #{period}"
227
240
  end
228
241
  if step
229
242
  now = Time.now
@@ -249,11 +262,13 @@ module Groupdate
249
262
  reverse = !reverse if options[:reverse]
250
263
 
251
264
  series =
252
- case field
265
+ case period
253
266
  when :day_of_week
254
267
  0..6
255
268
  when :hour_of_day
256
269
  0..23
270
+ when :minute_of_hour
271
+ 0..59
257
272
  when :day_of_month
258
273
  1..31
259
274
  when :month_of_year
@@ -277,10 +292,10 @@ module Groupdate
277
292
  if time_range.first
278
293
  series = [round_time(time_range.first)]
279
294
 
280
- if field == :quarter
295
+ if period == :quarter
281
296
  step = 3.months
282
297
  else
283
- step = 1.send(field)
298
+ step = 1.send(period)
284
299
  end
285
300
 
286
301
  last_step = series.last
@@ -322,9 +337,11 @@ module Groupdate
322
337
  else
323
338
  sunday = time_zone.parse("2014-03-02 00:00:00")
324
339
  lambda do |key|
325
- case field
340
+ case period
326
341
  when :hour_of_day
327
342
  key = sunday + key.hours + day_start.seconds
343
+ when :minute_of_hour
344
+ key = sunday + key.minutes + day_start.seconds
328
345
  when :day_of_week
329
346
  key = sunday + key.days
330
347
  when :day_of_month
@@ -335,7 +352,7 @@ module Groupdate
335
352
  I18n.localize(key, format: options[:format], locale: locale)
336
353
  end
337
354
  end
338
- elsif [:day, :week, :month, :quarter, :year].include?(field) && use_dates
355
+ elsif [:day, :week, :month, :quarter, :year].include?(period) && use_dates
339
356
  lambda { |k| k.to_date }
340
357
  else
341
358
  lambda { |k| k }
@@ -357,7 +374,7 @@ module Groupdate
357
374
  time = time.to_time.in_time_zone(time_zone) - day_start.seconds
358
375
 
359
376
  time =
360
- case field
377
+ case period
361
378
  when :second
362
379
  time.change(usec: 0)
363
380
  when :minute
@@ -378,6 +395,8 @@ module Groupdate
378
395
  time.beginning_of_year
379
396
  when :hour_of_day
380
397
  time.hour
398
+ when :minute_of_hour
399
+ time.min
381
400
  when :day_of_week
382
401
  time.wday
383
402
  when :day_of_month
@@ -385,7 +404,7 @@ module Groupdate
385
404
  when :month_of_year
386
405
  time.month
387
406
  else
388
- raise Groupdate::Error, "Invalid field"
407
+ raise Groupdate::Error, "Invalid period"
389
408
  end
390
409
 
391
410
  time.is_a?(Time) ? time + day_start.seconds : time
@@ -26,5 +26,9 @@ module Groupdate
26
26
  def reverse_order_value
27
27
  nil
28
28
  end
29
+
30
+ def unscoped
31
+ @relation.unscoped
32
+ end
29
33
  end
30
34
  end
@@ -1,3 +1,3 @@
1
1
  module Groupdate
2
- VERSION = "3.2.0"
2
+ VERSION = "3.2.1"
3
3
  end
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in groupdate.gemspec
4
+ gemspec path: "../../"
5
+
6
+ gem "activerecord", "~> 5.0.0"
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in groupdate.gemspec
4
+ gemspec path: "../../"
5
+
6
+ gem "activerecord", "~> 5.2.0.rc1"
data/test/sqlite_test.rb CHANGED
@@ -17,6 +17,11 @@ class TestSqlite < Minitest::Test
17
17
  skip
18
18
  end
19
19
 
20
+ def test_zeros_datetime
21
+ skip if ENV["TRAVIS"]
22
+ super
23
+ end
24
+
20
25
  def call_method(method, field, options)
21
26
  if method == :quarter || options[:time_zone] || options[:day_start] || options[:week_start] || Groupdate.week_start != :sun || (Time.zone && options[:time_zone] != false)
22
27
  error = assert_raises(Groupdate::Error) { super }
data/test/test_helper.rb CHANGED
@@ -413,6 +413,19 @@ module TestDatabase
413
413
  end
414
414
  end
415
415
 
416
+ # unscope
417
+
418
+ def test_unscope
419
+ assert_equal User.all, User.group_by_day(:created_at).unscoped.all
420
+ end
421
+
422
+ # pluck
423
+
424
+ def test_pluck
425
+ create_user "2014-05-01"
426
+ assert_equal [0], User.group_by_hour_of_day(:created_at).pluck(0)
427
+ end
428
+
416
429
  private
417
430
 
418
431
  def call_method(method, field, options)
@@ -431,8 +444,7 @@ module TestDatabase
431
444
  # hack for Redshift adapter, which doesn't return id on creation...
432
445
  user = User.last if user.id.nil?
433
446
 
434
- # hack for MySQL & Redshift adapters
435
- user.update_attributes(created_at: nil, created_on: nil) if created_at.nil? && is_redshift?
447
+ user.update_columns(created_at: nil, created_on: nil) if created_at.nil?
436
448
 
437
449
  user
438
450
  end
@@ -741,6 +753,16 @@ module TestGroupdate
741
753
  assert_result :hour_of_day, 0, "2013-01-01 10:00:00", true, day_start: 2
742
754
  end
743
755
 
756
+ # minute of hour
757
+
758
+ def test_minute_of_hour_end_of_hour
759
+ assert_result :minute_of_hour, 59, "2017-02-09 23:59:59"
760
+ end
761
+
762
+ def test_minute_of_hour_beginning_of_hour
763
+ assert_result :minute_of_hour, 0, "2017-02-09 00:00:00"
764
+ end
765
+
744
766
  # day of week
745
767
 
746
768
  def test_day_of_week_end_of_day
@@ -941,6 +963,15 @@ module TestGroupdate
941
963
  assert_equal expected, call_method(:hour_of_day, :created_at, {series: true})
942
964
  end
943
965
 
966
+ def test_zeros_minute_of_hour
967
+ create_user "2017-02-09 20:05:00 UTC"
968
+ expected = {}
969
+ 60.times do |n|
970
+ expected[n] = n == 5 ? 1 : 0
971
+ end
972
+ assert_equal expected, call_method(:minute_of_hour, :created_at, {series: true})
973
+ end
974
+
944
975
  def test_zeros_day_of_month
945
976
  create_user "1978-12-18"
946
977
  expected = {}
@@ -1045,6 +1076,16 @@ module TestGroupdate
1045
1076
  assert_format :hour_of_day, "12 am", "%-l %P", day_start: 2
1046
1077
  end
1047
1078
 
1079
+ def test_format_minute_of_hour
1080
+ create_user "2017-02-09"
1081
+ assert_format :minute_of_hour, "0", "%-M"
1082
+ end
1083
+
1084
+ def test_format_minute_of_hour_day_start
1085
+ create_user "2017-02-09"
1086
+ assert_format :minute_of_hour, "0", "%-M", day_start: 2
1087
+ end
1088
+
1048
1089
  def test_format_day_of_week
1049
1090
  create_user "2014-03-01"
1050
1091
  assert_format :day_of_week, "Sat", "%a"
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: 3.2.0
4
+ version: 3.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-31 00:00:00.000000000 Z
11
+ date: 2018-02-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '1.3'
33
+ version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '1.3'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -84,30 +84,30 @@ dependencies:
84
84
  name: pg
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ">="
87
+ - - "<"
88
88
  - !ruby/object:Gem::Version
89
- version: '0'
89
+ version: '1'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ">="
94
+ - - "<"
95
95
  - !ruby/object:Gem::Version
96
- version: '0'
96
+ version: '1'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: mysql2
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - "~>"
101
+ - - ">="
102
102
  - !ruby/object:Gem::Version
103
- version: 0.3.20
103
+ version: '0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - "~>"
108
+ - - ">="
109
109
  - !ruby/object:Gem::Version
110
- version: 0.3.20
110
+ version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: sqlite3
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -122,7 +122,7 @@ dependencies:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
- description: The simplest way to group temporal data
125
+ description:
126
126
  email:
127
127
  - andrew@chartkick.com
128
128
  executables: []
@@ -132,7 +132,9 @@ files:
132
132
  - ".gitignore"
133
133
  - ".travis.yml"
134
134
  - CHANGELOG.md
135
+ - CONTRIBUTING.md
135
136
  - Gemfile
137
+ - ISSUE_TEMPLATE.md
136
138
  - LICENSE.txt
137
139
  - README.md
138
140
  - Rakefile
@@ -152,6 +154,8 @@ files:
152
154
  - test/gemfiles/activerecord40.gemfile
153
155
  - test/gemfiles/activerecord41.gemfile
154
156
  - test/gemfiles/activerecord42.gemfile
157
+ - test/gemfiles/activerecord50.gemfile
158
+ - test/gemfiles/activerecord52.gemfile
155
159
  - test/gemfiles/redshift.gemfile
156
160
  - test/mysql_test.rb
157
161
  - test/postgresql_test.rb
@@ -178,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
178
182
  version: '0'
179
183
  requirements: []
180
184
  rubyforge_project:
181
- rubygems_version: 2.6.8
185
+ rubygems_version: 2.6.13
182
186
  signing_key:
183
187
  specification_version: 4
184
188
  summary: The simplest way to group temporal data
@@ -189,6 +193,8 @@ test_files:
189
193
  - test/gemfiles/activerecord40.gemfile
190
194
  - test/gemfiles/activerecord41.gemfile
191
195
  - test/gemfiles/activerecord42.gemfile
196
+ - test/gemfiles/activerecord50.gemfile
197
+ - test/gemfiles/activerecord52.gemfile
192
198
  - test/gemfiles/redshift.gemfile
193
199
  - test/mysql_test.rb
194
200
  - test/postgresql_test.rb