Commit e2bc9b5a82697b804ebd182702cfe341266364ae

Authored by Andrew Kane
2 parents 2847a806 077d4a96

Merge branch 'master' into rails5

CHANGELOG.md
1 1 ## 2.5.1 [unreleased]
2 2  
  3 +- Added `group_by_quarter`
3 4 - Added `default_value` option
4 5 - Accept symbol for `format` option
  6 +- Raise `ArgumentError` if no field specified
5 7  
6 8 ## 2.5.0
7 9  
... ...
README.md
... ... @@ -41,6 +41,7 @@ You can also group by:
41 41 - hour
42 42 - week
43 43 - month
  44 +- quarter
44 45 - year
45 46  
46 47 and
... ...
lib/groupdate.rb
... ... @@ -4,8 +4,10 @@ require "groupdate/version"
4 4 require "groupdate/magic"
5 5  
6 6 module Groupdate
7   - FIELDS = [:second, :minute, :hour, :day, :week, :month, :year, :day_of_week, :hour_of_day, :day_of_month, :month_of_year]
8   - METHODS = FIELDS.map { |v| :"group_by_#{v}" }
  7 + PERIODS = [:second, :minute, :hour, :day, :week, :month, :quarter, :year, :day_of_week, :hour_of_day, :day_of_month, :month_of_year]
  8 + # backwards compatibility for anyone who happened to use it
  9 + FIELDS = PERIODS
  10 + METHODS = PERIODS.map { |v| :"group_by_#{v}" }
9 11  
10 12 mattr_accessor :week_start, :day_start, :time_zone
11 13 self.week_start = :sun
... ...
lib/groupdate/enumerable.rb
1 1 module Enumerable
2   - Groupdate::FIELDS.each do |field|
3   - define_method :"group_by_#{field}" do |*args, &block|
  2 + Groupdate::PERIODS.each do |period|
  3 + define_method :"group_by_#{period}" do |options = {}, &block|
4 4 if block
5   - Groupdate::Magic.new(field, args[0] || {}).group_by(self, &block)
  5 + Groupdate::Magic.new(period, options).group_by(self, &block)
6 6 elsif respond_to?(:scoping)
7 7 scoping { @klass.send(:"group_by_#{field}", *args, &block) }
8 8 else
... ... @@ -13,7 +13,7 @@ module Enumerable
13 13  
14 14 def group_by_period(period, options = {}, &block)
15 15 # to_sym is unsafe on user input, so convert to strings
16   - permitted_periods = ((options[:permit] || Groupdate::FIELDS).map(&:to_sym) & Groupdate::FIELDS).map(&:to_s)
  16 + permitted_periods = ((options[:permit] || Groupdate::PERIODS).map(&:to_sym) & Groupdate::PERIODS).map(&:to_s)
17 17 if permitted_periods.include?(period.to_s)
18 18 send("group_by_#{period}", options, &block)
19 19 else
... ...
lib/groupdate/magic.rb
... ... @@ -45,6 +45,8 @@ module Groupdate
45 45 ["MONTH(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} HOUR), '+00:00', ?))", time_zone]
46 46 when :week
47 47 ["CONVERT_TZ(DATE_FORMAT(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL ((#{7 - week_start} + WEEKDAY(CONVERT_TZ(#{column}, '+00:00', ?) - INTERVAL #{day_start} HOUR)) % 7) DAY) - INTERVAL #{day_start} HOUR, '+00:00', ?), '%Y-%m-%d 00:00:00') + INTERVAL #{day_start} HOUR, ?, '+00:00')", time_zone, time_zone, time_zone]
  48 + when :quarter
  49 + ["DATE_ADD(CONVERT_TZ(DATE_FORMAT(DATE(CONCAT(EXTRACT(YEAR FROM CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} HOUR), '+00:00', ?)), '-', LPAD(1 + 3 * (QUARTER(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} HOUR), '+00:00', ?)) - 1), 2, '00'), '-01')), '%Y-%m-%d %H:%i:%S'), ?, '+00:00'), INTERVAL #{day_start} HOUR)", time_zone, time_zone, time_zone]
48 50 else
49 51 format =
50 52 case field
... ... @@ -201,7 +203,11 @@ module Groupdate
201 203 if time_range.first
202 204 series = [round_time(time_range.first)]
203 205  
204   - step = 1.send(field)
  206 + if field == :quarter
  207 + step = 3.months
  208 + else
  209 + step = 1.send(field)
  210 + end
205 211  
206 212 while (next_step = round_time(series.last + step)) && time_range.cover?(next_step)
207 213 series << next_step
... ... @@ -278,6 +284,8 @@ module Groupdate
278 284 (time - ((7 - week_start + weekday) % 7).days).midnight
279 285 when :month
280 286 time.beginning_of_month
  287 + when :quarter
  288 + time.beginning_of_quarter
281 289 when :year
282 290 time.beginning_of_year
283 291 when :hour_of_day
... ...
lib/groupdate/scopes.rb
1 1 module Groupdate
2 2 module Scopes
3   - Groupdate::FIELDS.each do |field|
4   - define_method :"group_by_#{field}" do |*args|
  3 + Groupdate::PERIODS.each do |period|
  4 + define_method :"group_by_#{period}" do |field, *args|
5 5 args = args.dup
6 6 options = args[-1].is_a?(Hash) ? args.pop : {}
7   - options[:time_zone] ||= args[1] unless args[1].nil?
8   - options[:range] ||= args[2] unless args[2].nil?
  7 + options[:time_zone] ||= args[0] unless args[0].nil?
  8 + options[:range] ||= args[1] unless args[1].nil?
9 9  
10   - Groupdate::Magic.new(field, options).relation(args[0], self)
  10 + Groupdate::Magic.new(period, options).relation(field, self)
11 11 end
12 12 end
13 13  
14 14 def group_by_period(period, field, options = {})
15 15 # to_sym is unsafe on user input, so convert to strings
16   - permitted_periods = ((options[:permit] || Groupdate::FIELDS).map(&:to_sym) & Groupdate::FIELDS).map(&:to_s)
  16 + permitted_periods = ((options[:permit] || Groupdate::PERIODS).map(&:to_sym) & Groupdate::PERIODS).map(&:to_s)
17 17 if permitted_periods.include?(period.to_s)
18 18 send("group_by_#{period}", field, options)
19 19 else
... ...
test/postgresql_test.rb
... ... @@ -7,4 +7,8 @@ class TestPostgresql &lt; Minitest::Test
7 7 super
8 8 User.establish_connection adapter: "postgresql", database: "groupdate_test"
9 9 end
  10 +
  11 + def test_no_column
  12 + assert_raises(ArgumentError) { User.group_by_day.first }
  13 + end
10 14 end
... ...
test/test_helper.rb
... ... @@ -230,6 +230,42 @@ module TestGroupdate
230 230 assert_result_time :month, "2013-03-01 02:00:00 PST", "2013-03-01 10:00:00", true, day_start: 2
231 231 end
232 232  
  233 + # quarter
  234 +
  235 + def test_quarter_end_of_quarter
  236 + assert_result_time :quarter, "2013-04-01 00:00:00 UTC", "2013-06-30 23:59:59"
  237 + end
  238 +
  239 + def test_quarter_start_of_quarter
  240 + assert_result_time :quarter, "2013-04-01 00:00:00 UTC", "2013-04-01 00:00:00"
  241 + end
  242 +
  243 + def test_quarter_end_of_quarter_with_time_zone
  244 + assert_result_time :quarter, "2013-04-01 00:00:00 PDT", "2013-07-01 06:59:59", true
  245 + end
  246 +
  247 + def test_quarter_start_of_quarter_with_time_zone
  248 + assert_result_time :quarter, "2013-04-01 00:00:00 PDT", "2013-04-01 07:00:00", true
  249 + end
  250 +
  251 + # quarter starts at 2am
  252 +
  253 + def test_quarter_end_of_quarter_day_start_2am
  254 + assert_result_time :quarter, "2013-04-01 02:00:00 UTC", "2013-07-01 01:59:59", false, day_start: 2
  255 + end
  256 +
  257 + def test_quarter_start_of_quarter_day_start_2am
  258 + assert_result_time :quarter, "2013-04-01 02:00:00 UTC", "2013-04-01 02:00:00", false, day_start: 2
  259 + end
  260 +
  261 + def test_quarter_end_of_quarter_with_time_zone_day_start_2am
  262 + assert_result_time :quarter, "2013-01-01 02:00:00 PST", "2013-04-01 08:59:59", true, day_start: 2
  263 + end
  264 +
  265 + def test_quarter_start_of_quarter_with_time_zone_day_start_2am
  266 + assert_result_time :quarter, "2013-01-01 02:00:00 PST", "2013-01-01 10:00:00", true, day_start: 2
  267 + end
  268 +
233 269 # year
234 270  
235 271 def test_year_end_of_year
... ... @@ -468,6 +504,14 @@ module TestGroupdate
468 504 assert_zeros :month, "2013-04-16 20:00:00 PDT", ["2013-03-01 00:00:00 PST", "2013-04-01 00:00:00 PDT", "2013-05-01 00:00:00 PDT"], "2013-03-01 00:00:00 PST", "2013-05-31 23:59:59 PDT", true
469 505 end
470 506  
  507 + def test_zeros_quarter
  508 + assert_zeros :quarter, "2013-04-16 20:00:00 UTC", ["2013-01-01 00:00:00 UTC", "2013-04-01 00:00:00 UTC", "2013-07-01 00:00:00 UTC"], "2013-01-01 00:00:00 UTC", "2013-09-30 23:59:59 UTC"
  509 + end
  510 +
  511 + def test_zeros_quarter_time_zone
  512 + assert_zeros :quarter, "2013-04-16 20:00:00 PDT", ["2013-01-01 00:00:00 PST", "2013-04-01 00:00:00 PDT", "2013-07-01 00:00:00 PDT"], "2013-01-01 00:00:00 PST", "2013-09-30 23:59:59 PDT", true
  513 + end
  514 +
471 515 def test_zeros_year
472 516 assert_zeros :year, "2013-04-16 20:00:00 UTC", ["2012-01-01 00:00:00 UTC", "2013-01-01 00:00:00 UTC", "2014-01-01 00:00:00 UTC"], "2012-01-01 00:00:00 UTC", "2014-12-31 23:59:59 UTC"
473 517 end
... ... @@ -723,6 +767,11 @@ module TestGroupdate
723 767 assert_format :month, "March 2014", "%B %Y"
724 768 end
725 769  
  770 + def test_format_quarter
  771 + create_user "2014-03-05 00:00:00 UTC"
  772 + assert_format :quarter, "January 1, 2014", "%B %-e, %Y"
  773 + end
  774 +
726 775 def test_format_year
727 776 create_user "2014-03-01 00:00:00 UTC"
728 777 assert_format :year, "2014", "%Y"
... ...