groupdate 3.2.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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"