groupdate 3.2.1 → 4.0.0

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: f98770b6255920a055b3d7ab6c9bf36227580077
4
- data.tar.gz: 4d7d1eb90c872e35ba89ad0eead165841f1e0506
3
+ metadata.gz: d6b498ed092c226ed3e7eeec42f68ff8a4b092c1
4
+ data.tar.gz: a43c5abd7af5dcc458825c8e151184fc30d98626
5
5
  SHA512:
6
- metadata.gz: 4aca35fb45ab76e77535404eb6123efc25bcdf4b2414b7255f8dd62e87e4b1d567eaf367807f93ac9ce18f5a571d752ea4d3dc2c47cc141413ebd49eeb656b31
7
- data.tar.gz: 3f4ade3ccaab2acc62a413ca93ff618b1d89c2daa0c4d341b895b76db055dd6c0b208826c12696eb4c3f83900afcea69560f6c9bdf8d34c15e3c781264785e1b
6
+ metadata.gz: c6379307ca9ef9c5d3435490bb61cea9d8afd15abd9c106aabbf55bb2e4d3d20f2dcd5f2e5e8e12cf338cb48c113d81ea2265e7085bce643b88a0011bd73c7f4
7
+ data.tar.gz: a1c6d9161d0051153b313b5501d3043df99de23105748e336a3c2446edeb5acfb3708be638c95cb23811205840f2dd2799f571124f369dbfbe562df70259087d
@@ -8,14 +8,12 @@ gemfile:
8
8
  - test/gemfiles/activerecord50.gemfile
9
9
  - test/gemfiles/activerecord42.gemfile
10
10
  sudo: false
11
- script: RUBYOPT=W0 bundle exec rake test
11
+ script: bundle exec rake test
12
12
  before_install:
13
13
  - gem install bundler
14
14
  - mysql -e 'create database groupdate_test;'
15
15
  - mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql
16
16
  - psql -c 'create database groupdate_test;' -U postgres
17
- env:
18
- - TRAVIS=t
19
17
  notifications:
20
18
  email:
21
19
  on_success: never
@@ -1,3 +1,15 @@
1
+ ## 4.0.0
2
+
3
+ - Custom calculation methods are supported by default - `groupdate_calculation_methods` is no longer needed
4
+
5
+ Breaking changes
6
+
7
+ - Dropped support for Rails < 4.2
8
+ - Invalid options now throw an `ArgumentError`
9
+ - `group_by` methods return an `ActiveRecord::Relation` instead of a `Groupdate::Series`
10
+ - `week_start` now affects `day_of_week`
11
+ - Removed support for `reverse_order` (was never supported in Rails 5)
12
+
1
13
  ## 3.2.1
2
14
 
3
15
  - Added `minute_of_hour`
@@ -60,6 +60,9 @@ git clone https://github.com/ankane/groupdate.git
60
60
  cd groupdate
61
61
  bundle install
62
62
  bundle exec rake test
63
+
64
+ # run a single test file
65
+ ruby test/postgresql_test.rb
63
66
  ```
64
67
 
65
68
  ---
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013 Andrew Kane
1
+ Copyright (c) 2013-2018 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -208,37 +208,6 @@ Count
208
208
  Hash[ users.group_by_day { |u| u.created_at }.map { |k, v| [k, v.size] } ]
209
209
  ```
210
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
-
242
211
  ## Installation
243
212
 
244
213
  Add this line to your application’s Gemfile:
@@ -281,6 +250,15 @@ Groupdate.time_zone = false
281
250
 
282
251
  ## Upgrading
283
252
 
253
+ ### 4.0
254
+
255
+ Groupdate 4.0 brings a number of improvements. Here are a few to be aware of:
256
+
257
+ - `group_by` methods return an `ActiveRecord::Relation` instead of a `Groupdate::Series`
258
+ - Invalid options now throw an `ArgumentError`
259
+ - `week_start` now affects `day_of_week`
260
+ - Custom calculation methods are supported by default
261
+
284
262
  ### 3.0
285
263
 
286
264
  Groupdate 3.0 brings a number of improvements. Here are a few to be aware of:
data/Rakefile CHANGED
@@ -7,18 +7,3 @@ Rake::TestTask.new do |t|
7
7
  t.test_files = FileList["test/**/*_test.rb"].exclude(/redshift/)
8
8
  t.warning = false
9
9
  end
10
-
11
- namespace :test do
12
- Rake::TestTask.new(:postgresql) do |t|
13
- t.libs << "test"
14
- t.pattern = "test/postgresql_test.rb"
15
- end
16
- Rake::TestTask.new(:mysql) do |t|
17
- t.libs << "test"
18
- t.pattern = "test/mysql_test.rb"
19
- end
20
- Rake::TestTask.new(:redshift) do |t|
21
- t.libs << "test"
22
- t.pattern = "test/redshift_test.rb"
23
- end
24
- end
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ["lib"]
19
19
 
20
- spec.add_dependency "activesupport", ">= 3"
20
+ spec.add_dependency "activesupport", ">= 4.2"
21
21
 
22
22
  spec.add_development_dependency "bundler"
23
23
  spec.add_development_dependency "rake"
@@ -7,8 +7,6 @@ module Groupdate
7
7
  class Error < RuntimeError; end
8
8
 
9
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
- # backwards compatibility for anyone who happened to use it
11
- FIELDS = PERIODS
12
10
  METHODS = PERIODS.map { |v| :"group_by_#{v}" } + [:group_by_period]
13
11
 
14
12
  mattr_accessor :week_start, :day_start, :time_zone, :dates
@@ -1,53 +1,6 @@
1
1
  require "active_record"
2
- require "groupdate/order_hack"
3
- require "groupdate/scopes"
4
- require "groupdate/calculations"
5
- require "groupdate/series"
2
+ require "groupdate/query_methods"
3
+ require "groupdate/relation"
6
4
 
7
- ActiveRecord::Base.send(:extend, Groupdate::Scopes)
8
-
9
- module ActiveRecord
10
- class Relation
11
- if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR < 2
12
-
13
- def method_missing_with_hack(method, *args, &block)
14
- if Groupdate::METHODS.include?(method)
15
- scoping { @klass.send(method, *args, &block) }
16
- else
17
- method_missing_without_hack(method, *args, &block)
18
- end
19
- end
20
- alias_method_chain :method_missing, :hack
21
-
22
- end
23
- end
24
- end
25
-
26
- module ActiveRecord
27
- module Associations
28
- class CollectionProxy
29
- if ActiveRecord::VERSION::MAJOR == 3
30
- delegate(*Groupdate::METHODS, to: :scoped)
31
- end
32
- end
33
- end
34
- end
35
-
36
- # hack for issue before Rails 5
37
- # https://github.com/rails/rails/issues/7121
38
- module ActiveRecord
39
- module Calculations
40
- private
41
-
42
- if ActiveRecord::VERSION::MAJOR < 5
43
- def column_alias_for_with_hack(*keys)
44
- if keys.first.is_a?(Groupdate::OrderHack)
45
- keys.first.field
46
- else
47
- column_alias_for_without_hack(*keys)
48
- end
49
- end
50
- alias_method_chain :column_alias_for, :hack
51
- end
52
- end
53
- end
5
+ ActiveRecord::Base.extend(Groupdate::QueryMethods)
6
+ ActiveRecord::Relation.include(Groupdate::Relation)
@@ -2,7 +2,7 @@ module Enumerable
2
2
  Groupdate::PERIODS.each do |period|
3
3
  define_method :"group_by_#{period}" do |*args, &block|
4
4
  if block
5
- Groupdate::Magic.new(period, args[0] || {}).group_by(self, &block)
5
+ Groupdate::Magic::Enumerable.group_by(self, period, args[0] || {}, &block)
6
6
  elsif respond_to?(:scoping)
7
7
  scoping { @klass.send(:"group_by_#{period}", *args, &block) }
8
8
  else
@@ -16,8 +16,9 @@ module Enumerable
16
16
  period = args[0]
17
17
  options = args[1] || {}
18
18
 
19
+ options = options.dup
19
20
  # to_sym is unsafe on user input, so convert to strings
20
- permitted_periods = ((options[:permit] || Groupdate::PERIODS).map(&:to_sym) & Groupdate::PERIODS).map(&:to_s)
21
+ permitted_periods = ((options.delete(:permit) || Groupdate::PERIODS).map(&:to_sym) & Groupdate::PERIODS).map(&:to_s)
21
22
  if permitted_periods.include?(period.to_s)
22
23
  send("group_by_#{period}", options, &block)
23
24
  else
@@ -4,205 +4,17 @@ module Groupdate
4
4
  class Magic
5
5
  attr_accessor :period, :options
6
6
 
7
- def initialize(period, options)
7
+ def initialize(period:, **options)
8
8
  @period = period
9
9
  @options = options
10
10
 
11
- raise Groupdate::Error, "Unrecognized time zone" unless time_zone
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?
12
13
 
14
+ raise Groupdate::Error, "Unrecognized time zone" unless time_zone
13
15
  raise Groupdate::Error, "Unrecognized :week_start option" if period == :week && !week_start
14
16
  end
15
17
 
16
- def group_by(enum, &_block)
17
- group = enum.group_by { |v| v = yield(v); v ? round_time(v) : nil }
18
- series(group, [], false, false, false)
19
- end
20
-
21
- def relation(column, relation)
22
- if relation.default_timezone == :local
23
- raise Groupdate::Error, "ActiveRecord::Base.default_timezone must be :utc to use Groupdate"
24
- end
25
-
26
- time_zone = self.time_zone.tzinfo.name
27
-
28
- adapter_name = relation.connection.adapter_name
29
- query =
30
- case adapter_name
31
- when "MySQL", "Mysql2", "Mysql2Spatial", 'Mysql2Rgeo'
32
- case period
33
- when :day_of_week # Sunday = 0, Monday = 1, etc
34
- # use CONCAT for consistent return type (String)
35
- ["DAYOFWEEK(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?)) - 1", time_zone]
36
- when :hour_of_day
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]
40
- when :day_of_month
41
- ["DAYOFMONTH(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?))", time_zone]
42
- when :month_of_year
43
- ["MONTH(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?))", time_zone]
44
- when :week
45
- ["CONVERT_TZ(DATE_FORMAT(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL ((#{7 - week_start} + WEEKDAY(CONVERT_TZ(#{column}, '+00:00', ?) - INTERVAL #{day_start} second)) % 7) DAY) - INTERVAL #{day_start} second, '+00:00', ?), '%Y-%m-%d 00:00:00') + INTERVAL #{day_start} second, ?, '+00:00')", time_zone, time_zone, time_zone]
46
- when :quarter
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]
48
- else
49
- format =
50
- case period
51
- when :second
52
- "%Y-%m-%d %H:%i:%S"
53
- when :minute
54
- "%Y-%m-%d %H:%i:00"
55
- when :hour
56
- "%Y-%m-%d %H:00:00"
57
- when :day
58
- "%Y-%m-%d 00:00:00"
59
- when :month
60
- "%Y-%m-01 00:00:00"
61
- else # year
62
- "%Y-01-01 00:00:00"
63
- end
64
-
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]
66
- end
67
- when "PostgreSQL", "PostGIS"
68
- case period
69
- when :day_of_week
70
- ["EXTRACT(DOW from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
71
- when :hour_of_day
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]
75
- when :day_of_month
76
- ["EXTRACT(DAY from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
77
- when :month_of_year
78
- ["EXTRACT(MONTH from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
79
- when :week # start on Sunday, not PostgreSQL default Monday
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]
81
- else
82
- ["(DATE_TRUNC('#{period}', (#{column}::timestamptz - INTERVAL '#{day_start} second') AT TIME ZONE ?) + INTERVAL '#{day_start} second') AT TIME ZONE ?", time_zone, time_zone]
83
- end
84
- when "SQLite"
85
- raise Groupdate::Error, "Time zones not supported for SQLite" unless self.time_zone.utc_offset.zero?
86
- raise Groupdate::Error, "day_start not supported for SQLite" unless day_start.zero?
87
- raise Groupdate::Error, "week_start not supported for SQLite" unless week_start == 6
88
-
89
- if period == :week
90
- ["strftime('%%Y-%%m-%%d 00:00:00 UTC', #{column}, '-6 days', 'weekday 0')"]
91
- else
92
- format =
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
119
-
120
- ["strftime('#{format.gsub(/%/, '%%')}', #{column})"]
121
- end
122
- when "Redshift"
123
- case period
124
- when :day_of_week # Sunday = 0, Monday = 1, etc.
125
- ["EXTRACT(DOW from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
126
- when :hour_of_day
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]
130
- when :day_of_month
131
- ["EXTRACT(DAY from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
132
- when :month_of_year
133
- ["EXTRACT(MONTH from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
134
- when :week # start on Sunday, not Redshift default Monday
135
- # Redshift does not return timezone information; it
136
- # always says it is in UTC time, so we must convert
137
- # back to UTC to play properly with the rest of Groupdate.
138
- #
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]
140
- else
141
- ["CONVERT_TIMEZONE(?, 'Etc/UTC', DATE_TRUNC(?, CONVERT_TIMEZONE(?, #{column}) - INTERVAL '#{day_start} second'))::timestamp + INTERVAL '#{day_start} second'", time_zone, period, time_zone]
142
- end
143
- else
144
- raise Groupdate::Error, "Connection adapter not supported: #{adapter_name}"
145
- end
146
-
147
- if adapter_name == "MySQL" && period == :week
148
- query[0] = "CAST(#{query[0]} AS DATETIME)"
149
- end
150
-
151
- group = relation.group(Groupdate::OrderHack.new(relation.send(:sanitize_sql_array, query), period, time_zone))
152
- relation =
153
- if time_range.is_a?(Range)
154
- # doesn't matter whether we include the end of a ... range - it will be excluded later
155
- group.where("#{column} >= ? AND #{column} <= ?", time_range.first, time_range.last)
156
- else
157
- group.where("#{column} IS NOT NULL")
158
- end
159
-
160
- # TODO do not change object state
161
- @group_index = group.group_values.size - 1
162
-
163
- Groupdate::Series.new(self, relation)
164
- end
165
-
166
- def perform(relation, method, *args, &block)
167
- # undo reverse since we do not want this to appear in the query
168
- reverse = relation.send(:reverse_order_value)
169
- relation = relation.except(:reverse_order) if reverse
170
- order = relation.order_values.first
171
- if order.is_a?(String)
172
- parts = order.split(" ")
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")
174
- if reverse_order
175
- reverse = !reverse
176
- relation = relation.reorder(relation.order_values[1..-1])
177
- end
178
- end
179
-
180
- multiple_groups = relation.group_values.size > 1
181
-
182
- cast_method =
183
- case period
184
- when :day_of_week, :hour_of_day, :day_of_month, :month_of_year, :minute_of_hour
185
- lambda { |k| k.to_i }
186
- else
187
- utc = ActiveSupport::TimeZone["UTC"]
188
- lambda { |k| (k.is_a?(String) || !k.respond_to?(:to_time) ? utc.parse(k.to_s) : k.to_time).in_time_zone(time_zone) }
189
- end
190
-
191
- result = relation.send(method, *args, &block)
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] }]
198
-
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
204
- end
205
-
206
18
  protected
207
19
 
208
20
  def time_zone
@@ -343,7 +155,7 @@ module Groupdate
343
155
  when :minute_of_hour
344
156
  key = sunday + key.minutes + day_start.seconds
345
157
  when :day_of_week
346
- key = sunday + key.days
158
+ key = sunday + key.days + (week_start + 1).days
347
159
  when :day_of_month
348
160
  key = Date.new(2014, 1, key).to_time
349
161
  when :month_of_year
@@ -398,7 +210,7 @@ module Groupdate
398
210
  when :minute_of_hour
399
211
  time.min
400
212
  when :day_of_week
401
- time.wday
213
+ (time.wday - 1 - week_start) % 7
402
214
  when :day_of_month
403
215
  time.day
404
216
  when :month_of_year
@@ -410,8 +222,219 @@ module Groupdate
410
222
  time.is_a?(Time) ? time + day_start.seconds : time
411
223
  end
412
224
 
413
- def activerecord42?
414
- ActiveRecord::VERSION::STRING.starts_with?("4.2.")
225
+ class Enumerable < Magic
226
+ def group_by(enum, &_block)
227
+ group = enum.group_by { |v| v = yield(v); v ? round_time(v) : nil }
228
+ series(group, [], false, false, false)
229
+ end
230
+
231
+ def self.group_by(enum, period, options, &block)
232
+ Groupdate::Magic::Enumerable.new(period: period, **options).group_by(enum, &block)
233
+ end
234
+ end
235
+
236
+ class Relation < Magic
237
+ def initialize(**options)
238
+ super(**options.reject { |k, _| [:default_value, :carry_forward, :last, :current].include?(k) })
239
+ @options = options
240
+ end
241
+
242
+ def relation(column, relation)
243
+ if relation.default_timezone == :local
244
+ raise Groupdate::Error, "ActiveRecord::Base.default_timezone must be :utc to use Groupdate"
245
+ end
246
+
247
+ time_zone = self.time_zone.tzinfo.name
248
+
249
+ adapter_name = relation.connection.adapter_name
250
+ query =
251
+ case adapter_name
252
+ when "MySQL", "Mysql2", "Mysql2Spatial", 'Mysql2Rgeo'
253
+ case period
254
+ when :day_of_week
255
+ ["DAYOFWEEK(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?)) - 1", time_zone]
256
+ when :hour_of_day
257
+ ["(EXTRACT(HOUR from CONVERT_TZ(#{column}, '+00:00', ?)) + 24 - #{day_start / 3600}) % 24", time_zone]
258
+ when :minute_of_hour
259
+ ["(EXTRACT(MINUTE from CONVERT_TZ(#{column}, '+00:00', ?)))", time_zone]
260
+ when :day_of_month
261
+ ["DAYOFMONTH(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?))", time_zone]
262
+ when :month_of_year
263
+ ["MONTH(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?))", time_zone]
264
+ when :week
265
+ ["CONVERT_TZ(DATE_FORMAT(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL ((#{7 - week_start} + WEEKDAY(CONVERT_TZ(#{column}, '+00:00', ?) - INTERVAL #{day_start} second)) % 7) DAY) - INTERVAL #{day_start} second, '+00:00', ?), '%Y-%m-%d 00:00:00') + INTERVAL #{day_start} second, ?, '+00:00')", time_zone, time_zone, time_zone]
266
+ when :quarter
267
+ ["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]
268
+ else
269
+ format =
270
+ case period
271
+ when :second
272
+ "%Y-%m-%d %H:%i:%S"
273
+ when :minute
274
+ "%Y-%m-%d %H:%i:00"
275
+ when :hour
276
+ "%Y-%m-%d %H:00:00"
277
+ when :day
278
+ "%Y-%m-%d 00:00:00"
279
+ when :month
280
+ "%Y-%m-01 00:00:00"
281
+ else # year
282
+ "%Y-01-01 00:00:00"
283
+ end
284
+
285
+ ["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]
286
+ end
287
+ when "PostgreSQL", "PostGIS"
288
+ case period
289
+ when :day_of_week
290
+ ["EXTRACT(DOW from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
291
+ when :hour_of_day
292
+ ["EXTRACT(HOUR from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
293
+ when :minute_of_hour
294
+ ["EXTRACT(MINUTE from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
295
+ when :day_of_month
296
+ ["EXTRACT(DAY from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
297
+ when :month_of_year
298
+ ["EXTRACT(MONTH from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
299
+ when :week # start on Sunday, not PostgreSQL default Monday
300
+ ["(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]
301
+ else
302
+ ["(DATE_TRUNC('#{period}', (#{column}::timestamptz - INTERVAL '#{day_start} second') AT TIME ZONE ?) + INTERVAL '#{day_start} second') AT TIME ZONE ?", time_zone, time_zone]
303
+ end
304
+ when "SQLite"
305
+ raise Groupdate::Error, "Time zones not supported for SQLite" unless self.time_zone.utc_offset.zero?
306
+ raise Groupdate::Error, "day_start not supported for SQLite" unless day_start.zero?
307
+ raise Groupdate::Error, "week_start not supported for SQLite" unless week_start == 6
308
+
309
+ if period == :week
310
+ ["strftime('%%Y-%%m-%%d 00:00:00 UTC', #{column}, '-6 days', 'weekday 0')"]
311
+ else
312
+ format =
313
+ case period
314
+ when :hour_of_day
315
+ "%H"
316
+ when :minute_of_hour
317
+ "%M"
318
+ when :day_of_week
319
+ "%w"
320
+ when :day_of_month
321
+ "%d"
322
+ when :month_of_year
323
+ "%m"
324
+ when :second
325
+ "%Y-%m-%d %H:%M:%S UTC"
326
+ when :minute
327
+ "%Y-%m-%d %H:%M:00 UTC"
328
+ when :hour
329
+ "%Y-%m-%d %H:00:00 UTC"
330
+ when :day
331
+ "%Y-%m-%d 00:00:00 UTC"
332
+ when :month
333
+ "%Y-%m-01 00:00:00 UTC"
334
+ when :quarter
335
+ raise Groupdate::Error, "Quarter not supported for SQLite"
336
+ else # year
337
+ "%Y-01-01 00:00:00 UTC"
338
+ end
339
+
340
+ ["strftime('#{format.gsub(/%/, '%%')}', #{column})"]
341
+ end
342
+ when "Redshift"
343
+ case period
344
+ when :day_of_week
345
+ ["EXTRACT(DOW from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
346
+ when :hour_of_day
347
+ ["EXTRACT(HOUR from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
348
+ when :minute_of_hour
349
+ ["EXTRACT(MINUTE from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
350
+ when :day_of_month
351
+ ["EXTRACT(DAY from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
352
+ when :month_of_year
353
+ ["EXTRACT(MONTH from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
354
+ when :week # start on Sunday, not Redshift default Monday
355
+ # Redshift does not return timezone information; it
356
+ # always says it is in UTC time, so we must convert
357
+ # back to UTC to play properly with the rest of Groupdate.
358
+ #
359
+ ["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]
360
+ else
361
+ ["CONVERT_TIMEZONE(?, 'Etc/UTC', DATE_TRUNC(?, CONVERT_TIMEZONE(?, #{column}) - INTERVAL '#{day_start} second'))::timestamp + INTERVAL '#{day_start} second'", time_zone, period, time_zone]
362
+ end
363
+ else
364
+ raise Groupdate::Error, "Connection adapter not supported: #{adapter_name}"
365
+ end
366
+
367
+ if adapter_name == "MySQL" && period == :week
368
+ query[0] = "CAST(#{query[0]} AS DATETIME)"
369
+ end
370
+
371
+ group_str = relation.send(:sanitize_sql_array, query)
372
+
373
+ # cleaner queries in logs
374
+ # Postgres
375
+ group_str = group_str.gsub(/ (\-|\+) INTERVAL '0 second'/, "")
376
+ # MySQL
377
+ group_str = group_str.gsub("DATE_SUB(#{column}, INTERVAL 0 second)", "#{column}")
378
+ if group_str.start_with?("DATE_ADD(") && group_str.end_with?(", INTERVAL 0 second)")
379
+ group_str = group_str[9..-21]
380
+ end
381
+
382
+ group = relation.group(group_str)
383
+ relation =
384
+ if time_range.is_a?(Range)
385
+ # doesn't matter whether we include the end of a ... range - it will be excluded later
386
+ group.where("#{column} >= ? AND #{column} <= ?", time_range.first, time_range.last)
387
+ else
388
+ group.where("#{column} IS NOT NULL")
389
+ end
390
+
391
+ # TODO do not change object state
392
+ @group_index = group.group_values.size - 1
393
+
394
+ relation
395
+ end
396
+
397
+ def perform(relation, result)
398
+ multiple_groups = relation.group_values.size > 1
399
+
400
+ cast_method =
401
+ case period
402
+ when :day_of_week
403
+ lambda { |k| (k.to_i - 1 - week_start) % 7 }
404
+ when :hour_of_day, :day_of_month, :month_of_year, :minute_of_hour
405
+ lambda { |k| k.to_i }
406
+ else
407
+ utc = ActiveSupport::TimeZone["UTC"]
408
+ lambda { |k| (k.is_a?(String) || !k.respond_to?(:to_time) ? utc.parse(k.to_s) : k.to_time).in_time_zone(time_zone) }
409
+ end
410
+
411
+ missing_time_zone_support = multiple_groups ? (result.keys.first && result.keys.first[@group_index].nil?) : result.key?(nil)
412
+ if missing_time_zone_support
413
+ raise Groupdate::Error, "Be sure to install time zone support - https://github.com/ankane/groupdate#for-mysql"
414
+ end
415
+ 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] }]
416
+
417
+ series(result, (options.key?(:default_value) ? options[:default_value] : 0), multiple_groups, @reverse)
418
+ end
419
+
420
+ def self.generate_relation(relation, field:, **options)
421
+ magic = Groupdate::Magic::Relation.new(**options)
422
+
423
+ # generate ActiveRecord relation
424
+ relation = magic.relation(field, relation)
425
+
426
+ # add Groupdate info
427
+ (relation.groupdate_values ||= []) << magic
428
+
429
+ relation
430
+ end
431
+
432
+ def self.process_result(relation, result)
433
+ relation.groupdate_values.reverse.each do |gv|
434
+ result = gv.perform(relation, result)
435
+ end
436
+ result
437
+ end
415
438
  end
416
439
  end
417
440
  end
@@ -0,0 +1,25 @@
1
+ module Groupdate
2
+ module QueryMethods
3
+ Groupdate::PERIODS.each do |period|
4
+ define_method :"group_by_#{period}" do |field, time_zone = nil, range = nil, **options|
5
+ Groupdate::Magic::Relation.generate_relation(self,
6
+ period: period,
7
+ field: field,
8
+ time_zone: time_zone,
9
+ range: range,
10
+ **options
11
+ )
12
+ end
13
+ end
14
+
15
+ def group_by_period(period, field, permit: nil, **options)
16
+ # to_sym is unsafe on user input, so convert to strings
17
+ permitted_periods = ((permit || Groupdate::PERIODS).map(&:to_sym) & Groupdate::PERIODS).map(&:to_s)
18
+ if permitted_periods.include?(period.to_s)
19
+ send("group_by_#{period}", field, **options)
20
+ else
21
+ raise ArgumentError, "Unpermitted period"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ require "active_support/concern"
2
+
3
+ module Groupdate
4
+ module Relation
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ attr_accessor :groupdate_values
9
+ end
10
+
11
+ def calculate(*args, &block)
12
+ if groupdate_values
13
+ Groupdate::Magic::Relation.process_result(self, super)
14
+ else
15
+ super
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,3 +1,3 @@
1
1
  module Groupdate
2
- VERSION = "3.2.1"
2
+ VERSION = "4.0.0"
3
3
  end
@@ -3,5 +3,5 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in groupdate.gemspec
4
4
  gemspec path: "../../"
5
5
 
6
- gem "activerecord", "~> 4.2.0"
7
- gem "activerecord4-redshift-adapter", "~> 0.2.0"
6
+ gem "activerecord", "~> 5.1.0"
7
+ gem "activerecord5-redshift-adapter"
@@ -17,11 +17,6 @@ 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
-
25
20
  def call_method(method, field, options)
26
21
  if method == :quarter || options[:time_zone] || options[:day_start] || options[:week_start] || Groupdate.week_start != :sun || (Time.zone && options[:time_zone] != false)
27
22
  error = assert_raises(Groupdate::Error) { super }
@@ -10,7 +10,7 @@ Minitest::Test = Minitest::Unit::TestCase unless defined?(Minitest::Test)
10
10
  ENV["TZ"] = "UTC"
11
11
 
12
12
  # for debugging
13
- # ActiveRecord::Base.logger = Logger.new(STDOUT)
13
+ ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT) if ENV["VERBOSE"]
14
14
 
15
15
  # rails does this in activerecord/lib/active_record/railtie.rb
16
16
  ActiveRecord::Base.default_timezone = :utc
@@ -19,17 +19,9 @@ ActiveRecord::Base.time_zone_aware_attributes = true
19
19
  class User < ActiveRecord::Base
20
20
  has_many :posts
21
21
 
22
- def self.groupdate_calculation_methods
23
- [:custom_count, :undefined_calculation]
24
- end
25
-
26
22
  def self.custom_count
27
23
  count
28
24
  end
29
-
30
- def self.unlisted_calculation
31
- count
32
- end
33
25
  end
34
26
 
35
27
  class Post < ActiveRecord::Base
@@ -86,24 +78,6 @@ module TestDatabase
86
78
  assert_equal expected, User.where("id = 0").group_by_day(:created_at, range: Date.parse("2013-05-01")..Date.parse("2013-05-01 23:59:59 UTC")).count
87
79
  end
88
80
 
89
- def test_order_hour_of_day
90
- assert_equal 23, User.group_by_hour_of_day(:created_at).order("hour_of_day desc").count.keys.first
91
- end
92
-
93
- def test_order_hour_of_day_case
94
- assert_equal 23, User.group_by_hour_of_day(:created_at).order("hour_of_day DESC").count.keys.first
95
- end
96
-
97
- def test_order_hour_of_day_reverse
98
- skip if ActiveRecord::VERSION::MAJOR == 5
99
- assert_equal 23, User.group_by_hour_of_day(:created_at).reverse_order.count.keys.first
100
- end
101
-
102
- def test_order_hour_of_day_order_reverse
103
- skip if ActiveRecord::VERSION::MAJOR == 5
104
- assert_equal 0, User.group_by_hour_of_day(:created_at).order("hour_of_day desc").reverse_order.count.keys.first
105
- end
106
-
107
81
  def test_table_name
108
82
  # This test is to ensure there's not an error when using the table
109
83
  # name as part of the column name.
@@ -403,10 +377,6 @@ module TestDatabase
403
377
  assert_equal expected, User.group_by_day(:created_at).custom_count
404
378
  end
405
379
 
406
- def test_using_unlisted_calculation_method_returns_new_series_instance
407
- assert_instance_of Groupdate::Series, User.group_by_day(:created_at).unlisted_calculation
408
- end
409
-
410
380
  def test_using_listed_but_undefined_custom_calculation_method_raises_error
411
381
  assert_raises(NoMethodError) do
412
382
  User.group_by_day(:created_at).undefined_calculation
@@ -426,6 +396,12 @@ module TestDatabase
426
396
  assert_equal [0], User.group_by_hour_of_day(:created_at).pluck(0)
427
397
  end
428
398
 
399
+ # test relation
400
+
401
+ def test_relation
402
+ assert User.group_by_day(:created_at).is_a?(ActiveRecord::Relation)
403
+ end
404
+
429
405
  private
430
406
 
431
407
  def call_method(method, field, options)
@@ -799,6 +775,24 @@ module TestGroupdate
799
775
  assert_result :day_of_week, 3, "2013-01-02 10:00:00", true, day_start: 2
800
776
  end
801
777
 
778
+ # day of week week start monday
779
+
780
+ def test_day_of_week_end_of_day_week_start_mon
781
+ assert_result :day_of_week, 1, "2013-01-01 23:59:59", false, week_start: :mon
782
+ end
783
+
784
+ def test_day_of_week_start_of_day_week_start_mon
785
+ assert_result :day_of_week, 2, "2013-01-02 00:00:00", false, week_start: :mon
786
+ end
787
+
788
+ def test_day_of_week_end_of_week_with_time_zone_week_start_mon
789
+ assert_result :day_of_week, 1, "2013-01-02 07:59:59", true, week_start: :mon
790
+ end
791
+
792
+ def test_day_of_week_start_of_week_with_time_zone_week_start_mon
793
+ assert_result :day_of_week, 2, "2013-01-02 08:00:00", true, week_start: :mon
794
+ end
795
+
802
796
  # day of month
803
797
 
804
798
  def test_day_of_month_end_of_day
@@ -1101,6 +1095,10 @@ module TestGroupdate
1101
1095
  assert_format :day_of_week, "Sat", "%a", week_start: :mon
1102
1096
  end
1103
1097
 
1098
+ def test_format_day_of_week_week_start
1099
+ assert_equal "Mon", call_method(:day_of_week, :created_at, week_start: :mon, format: "%a", series: true).keys.first
1100
+ end
1101
+
1104
1102
  def test_format_day_of_month
1105
1103
  create_user "2014-03-01"
1106
1104
  assert_format :day_of_month, " 1", "%e"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: groupdate
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.1
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '3'
19
+ version: '4.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '3'
26
+ version: '4.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -141,18 +141,12 @@ files:
141
141
  - groupdate.gemspec
142
142
  - lib/groupdate.rb
143
143
  - lib/groupdate/active_record.rb
144
- - lib/groupdate/calculations.rb
145
144
  - lib/groupdate/enumerable.rb
146
145
  - lib/groupdate/magic.rb
147
- - lib/groupdate/order_hack.rb
148
- - lib/groupdate/scopes.rb
149
- - lib/groupdate/series.rb
146
+ - lib/groupdate/query_methods.rb
147
+ - lib/groupdate/relation.rb
150
148
  - lib/groupdate/version.rb
151
149
  - test/enumerable_test.rb
152
- - test/gemfiles/activerecord31.gemfile
153
- - test/gemfiles/activerecord32.gemfile
154
- - test/gemfiles/activerecord40.gemfile
155
- - test/gemfiles/activerecord41.gemfile
156
150
  - test/gemfiles/activerecord42.gemfile
157
151
  - test/gemfiles/activerecord50.gemfile
158
152
  - test/gemfiles/activerecord52.gemfile
@@ -188,10 +182,6 @@ specification_version: 4
188
182
  summary: The simplest way to group temporal data
189
183
  test_files:
190
184
  - test/enumerable_test.rb
191
- - test/gemfiles/activerecord31.gemfile
192
- - test/gemfiles/activerecord32.gemfile
193
- - test/gemfiles/activerecord40.gemfile
194
- - test/gemfiles/activerecord41.gemfile
195
185
  - test/gemfiles/activerecord42.gemfile
196
186
  - test/gemfiles/activerecord50.gemfile
197
187
  - test/gemfiles/activerecord52.gemfile
@@ -1,26 +0,0 @@
1
- module Groupdate
2
- class Calculations
3
- attr_reader :relation
4
-
5
- def initialize(relation)
6
- @relation = relation
7
- end
8
-
9
- def include?(method)
10
- # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/calculations.rb
11
- ActiveRecord::Calculations.method_defined?(method) || custom_calculations.include?(method)
12
- end
13
-
14
- def custom_calculations
15
- return [] if !model.respond_to?(:groupdate_calculation_methods)
16
- model.groupdate_calculation_methods
17
- end
18
-
19
- private
20
-
21
- def model
22
- return if !relation.respond_to?(:klass)
23
- relation.klass
24
- end
25
- end
26
- end
@@ -1,11 +0,0 @@
1
- module Groupdate
2
- class OrderHack < String
3
- attr_reader :field, :time_zone
4
-
5
- def initialize(str, field, time_zone)
6
- super(str)
7
- @field = field.to_s
8
- @time_zone = time_zone
9
- end
10
- end
11
- end
@@ -1,24 +0,0 @@
1
- module Groupdate
2
- module Scopes
3
- Groupdate::PERIODS.each do |period|
4
- define_method :"group_by_#{period}" do |field, *args|
5
- args = args.dup
6
- options = args[-1].is_a?(Hash) ? args.pop : {}
7
- options[:time_zone] ||= args[0] unless args[0].nil?
8
- options[:range] ||= args[1] unless args[1].nil?
9
-
10
- Groupdate::Magic.new(period, options).relation(field, self)
11
- end
12
- end
13
-
14
- def group_by_period(period, field, options = {})
15
- # to_sym is unsafe on user input, so convert to strings
16
- permitted_periods = ((options[:permit] || Groupdate::PERIODS).map(&:to_sym) & Groupdate::PERIODS).map(&:to_s)
17
- if permitted_periods.include?(period.to_s)
18
- send("group_by_#{period}", field, options)
19
- else
20
- raise ArgumentError, "Unpermitted period"
21
- end
22
- end
23
- end
24
- end
@@ -1,34 +0,0 @@
1
- module Groupdate
2
- class Series
3
- attr_accessor :magic, :relation
4
-
5
- def initialize(magic, relation)
6
- @magic = magic
7
- @relation = relation
8
- @calculations = Groupdate::Calculations.new(relation)
9
- end
10
-
11
- # clone to prevent modifying original variables
12
- def method_missing(method, *args, &block)
13
- if @calculations.include?(method)
14
- magic.perform(relation, method, *args, &block)
15
- elsif relation.respond_to?(method, true)
16
- Groupdate::Series.new(magic, relation.send(method, *args, &block))
17
- else
18
- super
19
- end
20
- end
21
-
22
- def respond_to?(method, include_all = false)
23
- @calculations.include?(method) || relation.respond_to?(method) || super
24
- end
25
-
26
- def reverse_order_value
27
- nil
28
- end
29
-
30
- def unscoped
31
- @relation.unscoped
32
- end
33
- end
34
- end
@@ -1,6 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in groupdate.gemspec
4
- gemspec path: "../../"
5
-
6
- gem "activerecord", "~> 3.1.0"
@@ -1,6 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in groupdate.gemspec
4
- gemspec path: "../../"
5
-
6
- gem "activerecord", "~> 3.2.0"
@@ -1,6 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in groupdate.gemspec
4
- gemspec path: "../../"
5
-
6
- gem "activerecord", "~> 4.0.0"
@@ -1,6 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in groupdate.gemspec
4
- gemspec path: "../../"
5
-
6
- gem "activerecord", "~> 4.1.0"