groupdate 3.2.0 → 3.2.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 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