groupdate 2.0.4 → 2.1.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: 30c3377f6edb6fe33f5d0ac0aab691a6a7a6c198
4
- data.tar.gz: d725a9a605553a390055a4bbf55a132233aade70
3
+ metadata.gz: 08308d10dc780963998b4ebcb906f802dd15f6a7
4
+ data.tar.gz: 4e2a7f3a352b94f22287e2e84e4e8830625914ca
5
5
  SHA512:
6
- metadata.gz: d121c447a7c6c4c6b9be3e28942c640ea4f7bb5da420314eb863d7ff9a1a648c77129afe726ae3311a61cef6a32d386fbf76e97948adc0313f0a87935f4fe181
7
- data.tar.gz: 13e3ba1f7530160f450cb48aa1e5e67f65af979602e2c3ff0d392cc436079db1c12cd457c122e30d63e1c5cac75001af3dafb4b3839520402d1b115015200613
6
+ metadata.gz: d302a57517c823e80b7192a42153bc4a594af23bd1647f6cd7f3eab77a57252882204e22d40e4a6a3a80253be4adce6577cb9fa723d52342f7f8d8cf4a725d77
7
+ data.tar.gz: 0fdeb289ba29919be219e0dc1cdaf040552ffdc2ffa1ed5c180178ba8c8f4bb8aa00d8ffb0862eb5030af8996144098b2d03380c96d0a5f97ce248362b148578
@@ -1,3 +1,8 @@
1
+ # 2.1.0
2
+
3
+ - added last option
4
+ - added format option
5
+
1
6
  # 2.0.4
2
7
 
3
8
  - added multiple groups
data/README.md CHANGED
@@ -107,6 +107,12 @@ To get a specific time range, use:
107
107
  User.group_by_day(:created_at, range: 2.weeks.ago.midnight..Time.now).count
108
108
  ```
109
109
 
110
+ To get the most recent time periods, use:
111
+
112
+ ```ruby
113
+ User.group_by_week(:created_at, last: 8).count # last 8 weeks
114
+ ```
115
+
110
116
  ### Order
111
117
 
112
118
  You can order in descending order with:
@@ -121,6 +127,16 @@ or
121
127
  User.group_by_day(:created_at).order("day desc").count
122
128
  ```
123
129
 
130
+ ### Pretty Keys
131
+
132
+ To get keys in a different format, use:
133
+
134
+ ```ruby
135
+ User.group_by_hour_of_day(:created_at, format: "%l %P").count.keys.first # 12 am
136
+ ```
137
+
138
+ Takes a `String`, which is passed to [strftime](http://strfti.me/), or a `Proc`
139
+
124
140
  ## Installation
125
141
 
126
142
  Add this line to your application’s Gemfile:
@@ -76,7 +76,7 @@ module Groupdate
76
76
  group = group(Groupdate::OrderHack.new(sanitize_sql_array(query), field, time_zone))
77
77
  range = args[2] || options[:range] || true
78
78
  unless options[:series] == false
79
- Series.new(group, field, column, time_zone_object, range, week_start, day_start, group.group_values.size - 1)
79
+ Series.new(group, field, column, time_zone_object, range, week_start, day_start, group.group_values.size - 1, options)
80
80
  else
81
81
  group
82
82
  end
@@ -1,7 +1,8 @@
1
1
  module Groupdate
2
2
  class Series
3
+ attr_accessor :relation
3
4
 
4
- def initialize(relation, field, column, time_zone, time_range, week_start, day_start, group_index)
5
+ def initialize(relation, field, column, time_zone, time_range, week_start, day_start, group_index, options)
5
6
  @relation = relation
6
7
  @field = field
7
8
  @column = column
@@ -10,20 +11,42 @@ module Groupdate
10
11
  @week_start = week_start
11
12
  @day_start = day_start
12
13
  @group_index = group_index
14
+ @options = options
13
15
  end
14
16
 
15
- def build_series(count)
17
+ def perform(method, *args, &block)
16
18
  utc = ActiveSupport::TimeZone["UTC"]
17
19
 
18
- reverse = @reverse
19
- order = @relation.order_values.first
20
+ time_range = @time_range
21
+ if !time_range.is_a?(Range) and @options[:last]
22
+ step = 1.send(@field) if 1.respond_to?(@field)
23
+ if step
24
+ now = Time.now
25
+ time_range = round_time(now - (@options[:last].to_i - 1).send(@field))..now
26
+ end
27
+ end
28
+
29
+ relation =
30
+ if time_range.is_a?(Range)
31
+ # doesn't matter whether we include the end of a ... range - it will be excluded later
32
+ @relation.where("#{@column} >= ? AND #{@column} <= ?", time_range.first, time_range.last)
33
+ else
34
+ @relation.where("#{@column} IS NOT NULL")
35
+ end
36
+
37
+ # undo reverse since we do not want this to appear in the query
38
+ reverse = relation.reverse_order_value
39
+ if reverse
40
+ relation = relation.reverse_order
41
+ end
42
+ order = relation.order_values.first
20
43
  if order.is_a?(String)
21
44
  parts = order.split(" ")
22
45
  reverse_order = (parts.size == 2 && parts[0] == @field && parts[1].to_s.downcase == "desc")
23
46
  reverse = !reverse if reverse_order
24
47
  end
25
48
 
26
- multiple_groups = @relation.group_values.size > 1
49
+ multiple_groups = relation.group_values.size > 1
27
50
 
28
51
  cast_method =
29
52
  case @field
@@ -33,7 +56,7 @@ module Groupdate
33
56
  lambda{|k| (k.is_a?(String) ? utc.parse(k) : k.to_time).in_time_zone(@time_zone) }
34
57
  end
35
58
 
36
- count = Hash[ count.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] } ]
59
+ count = Hash[ relation.send(method, *args, &block).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] } ]
37
60
 
38
61
  series =
39
62
  case @field
@@ -43,8 +66,8 @@ module Groupdate
43
66
  0..23
44
67
  else
45
68
  time_range =
46
- if @time_range.is_a?(Range)
47
- @time_range
69
+ if time_range.is_a?(Range)
70
+ time_range
48
71
  else
49
72
  # use first and last values
50
73
  sorted_keys =
@@ -57,30 +80,7 @@ module Groupdate
57
80
  end
58
81
 
59
82
  if time_range.first
60
- # determine start time
61
- time = time_range.first.to_time.in_time_zone(@time_zone) - @day_start.hours
62
- starts_at =
63
- case @field
64
- when "second"
65
- time.change(:usec => 0)
66
- when "minute"
67
- time.change(:sec => 0)
68
- when "hour"
69
- time.change(:min => 0)
70
- when "day"
71
- time.beginning_of_day
72
- when "week"
73
- # same logic as MySQL group
74
- weekday = (time.wday - 1) % 7
75
- (time - ((7 - @week_start + weekday) % 7).days).midnight
76
- when "month"
77
- time.beginning_of_month
78
- else # year
79
- time.beginning_of_year
80
- end
81
-
82
- starts_at += @day_start.hours
83
- series = [starts_at]
83
+ series = [round_time(time_range.first)]
84
84
 
85
85
  step = 1.send(@field)
86
86
 
@@ -107,31 +107,70 @@ module Groupdate
107
107
  series = series.to_a.reverse
108
108
  end
109
109
 
110
+ key_format =
111
+ if @options[:format]
112
+ if @options[:format].respond_to?(:call)
113
+ @options[:format]
114
+ else
115
+ sunday = @time_zone.parse("2014-03-02 00:00:00")
116
+ lambda do |key|
117
+ case @field
118
+ when "hour_of_day"
119
+ key = sunday + key.hours + @day_start.hours
120
+ when "day_of_week"
121
+ key = sunday + key.days
122
+ end
123
+ key.strftime(@options[:format].to_s)
124
+ end
125
+ end
126
+ else
127
+ lambda{|k| k }
128
+ end
129
+
110
130
  Hash[series.map do |k|
111
- [k, count[k] || 0]
131
+ [key_format.call(k), count[k] || 0]
112
132
  end]
113
133
  end
114
134
 
115
- def method_missing(method, *args, &block)
116
- # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/calculations.rb
117
- if ActiveRecord::Calculations.method_defined?(method)
118
- relation =
119
- if @time_range.is_a?(Range)
120
- # doesn't matter whether we include the end of a ... range - it will be excluded later
121
- @relation.where("#{@column} >= ? AND #{@column} <= ?", @time_range.first, @time_range.last)
122
- else
123
- @relation.where("#{@column} IS NOT NULL")
124
- end
135
+ def round_time(time)
136
+ time = time.to_time.in_time_zone(@time_zone) - @day_start.hours
125
137
 
126
- # undo reverse since we do not want this to appear in the query
127
- if relation.reverse_order_value
128
- @reverse = true
129
- relation = relation.reverse_order
138
+ time =
139
+ case @field
140
+ when "second"
141
+ time.change(:usec => 0)
142
+ when "minute"
143
+ time.change(:sec => 0)
144
+ when "hour"
145
+ time.change(:min => 0)
146
+ when "day"
147
+ time.beginning_of_day
148
+ when "week"
149
+ # same logic as MySQL group
150
+ weekday = (time.wday - 1) % 7
151
+ (time - ((7 - @week_start + weekday) % 7).days).midnight
152
+ when "month"
153
+ time.beginning_of_month
154
+ else # year
155
+ time.beginning_of_year
130
156
  end
131
157
 
132
- build_series(relation.send(method, *args, &block))
158
+ time + @day_start.hours
159
+ end
160
+
161
+ def clone
162
+ Groupdate::Series.new(@relation, @field, @column, @time_zone, @time_range, @week_start, @day_start, @group_index, @options)
163
+ end
164
+
165
+ # clone to prevent modifying original variables
166
+ def method_missing(method, *args, &block)
167
+ # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/calculations.rb
168
+ if ActiveRecord::Calculations.method_defined?(method)
169
+ clone.perform(method, *args, &block)
133
170
  elsif @relation.respond_to?(method)
134
- Groupdate::Series.new(@relation.send(method, *args, &block), @field, @column, @time_zone, @time_range, @week_start, @day_start, @group_index)
171
+ series = clone
172
+ series.relation = @relation.send(method, *args, &block)
173
+ series
135
174
  else
136
175
  super
137
176
  end
@@ -1,3 +1,3 @@
1
1
  module Groupdate
2
- VERSION = "2.0.4"
2
+ VERSION = "2.1.0"
3
3
  end
@@ -549,8 +549,58 @@ module TestGroupdate
549
549
  assert !User.group_by_day(:created_at).respond_to?(:no_such_method)
550
550
  end
551
551
 
552
+ def test_last
553
+ create_user "2011-05-01 00:00:00 UTC"
554
+ create_user "2013-05-01 00:00:00 UTC"
555
+ expected = {
556
+ utc.parse("2012-01-01 00:00:00 UTC") => 0,
557
+ utc.parse("2013-01-01 00:00:00 UTC") => 1,
558
+ utc.parse("2014-01-01 00:00:00 UTC") => 0
559
+ }
560
+ assert_equal expected, User.group_by_year(:created_at, last: 3).count
561
+ end
562
+
563
+ def test_format_day
564
+ create_user "2014-03-01 00:00:00 UTC"
565
+ assert_format :day, "March 1, 2014", "%B %-e, %Y"
566
+ end
567
+
568
+ def test_format_month
569
+ create_user "2014-03-01 00:00:00 UTC"
570
+ assert_format :month, "March 2014", "%B %Y"
571
+ end
572
+
573
+ def test_format_year
574
+ create_user "2014-03-01 00:00:00 UTC"
575
+ assert_format :year, "2014", "%Y"
576
+ end
577
+
578
+ def test_format_hour_of_day
579
+ create_user "2014-03-01 00:00:00 UTC"
580
+ assert_format :hour_of_day, "12 am", "%-l %P"
581
+ end
582
+
583
+ def test_format_hour_of_day_day_start
584
+ create_user "2014-03-01 00:00:00 UTC"
585
+ assert_format :hour_of_day, "2 am", "%-l %P", day_start: 2
586
+ end
587
+
588
+ def test_format_day_of_week
589
+ create_user "2014-03-01 00:00:00 UTC"
590
+ assert_format :day_of_week, "Sun", "%a"
591
+ end
592
+
593
+ def test_format_day_of_week_week_start
594
+ create_user "2014-03-01 00:00:00 UTC"
595
+ assert_format :day_of_week, "Sun", "%a", week_start: :sat
596
+ end
597
+
552
598
  # helpers
553
599
 
600
+ def assert_format(method, expected, format, options = {})
601
+ assert_equal expected, User.send(:"group_by_#{method}", :created_at, options.merge(format: format)).count.keys.first
602
+ end
603
+
554
604
  def assert_result_time(method, expected, time_str, time_zone = false, options = {})
555
605
  expected = {utc.parse(expected).in_time_zone(time_zone ? "Pacific Time (US & Canada)" : utc) => 1}
556
606
  assert_equal expected, result(method, time_str, time_zone, options)
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: 2.0.4
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-13 00:00:00.000000000 Z
11
+ date: 2014-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord