groupdate 0.1.4 → 0.1.5

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: 96aed6d659f986f853f50f45bc93663f884235ac
4
- data.tar.gz: b33a101116de8a25f3fde64ab7824aeb5e09d72b
3
+ metadata.gz: 81ffab786c4714072fad2155f8475839010822af
4
+ data.tar.gz: 33dd72742440609989c5c61cb267574f8a5611b3
5
5
  SHA512:
6
- metadata.gz: a7756158b511af490af7f6c6c6f516674161c5e2f5957380d4d6c7eb30cf733f25b4d7acdd203d9f835c7a98b9126ec91c3027910f71717eeb455ad993c37200
7
- data.tar.gz: 3c1998ad9aec8d3a93001e86ad6d4e54d43547cc32689a32a247566b3bb010b9792799077a5c9485fbfdb906cf2f830d080d3c05856a432988d5d9764c75637b
6
+ metadata.gz: 0df29d96f26fb544e20d768f980c930cf53806c3a4da0ba1e1e4963a814ad8fee09049c3996d9e3f45c9547cdd3147effcf9b3d22ef2deff06170fb2f75b1023
7
+ data.tar.gz: 75b9c0e7c01cd1d936e4fbd9832a460827a1340d6a95b17fdde3a1898236edacc26daf9a87968e38f9210222083d7717b92bfcfa016090eefcd291f09f1ab083
data/README.md CHANGED
@@ -9,7 +9,9 @@ The simplest way to group by:
9
9
  - hour of the day
10
10
  - and more (complete list at bottom)
11
11
 
12
- :tada: Time zones supported!! **The best part**
12
+ :tada: Time zones supported!! **the best part**
13
+
14
+ :cake: Get the entire series - **the other best part**
13
15
 
14
16
  Works with Rails 3.0+
15
17
 
@@ -103,6 +105,55 @@ Go nuts!
103
105
  Request.where(page: "/home").group_by_minute(:started_at).maximum(:request_time)
104
106
  ```
105
107
 
108
+ ### Show me the series :moneybag:
109
+
110
+ You have two users - one created on May 2 and one on May 5.
111
+
112
+ ```ruby
113
+ User.group_by_day(:created_at).count
114
+ # {
115
+ # 2013-05-02 00:00:00 UTC => 1,
116
+ # 2013-05-05 00:00:00 UTC => 1
117
+ # }
118
+ ```
119
+
120
+ Awesome, but you want to see the first week of May. Pass a range as the third argument.
121
+
122
+ ```ruby
123
+ # pretend today is May 7
124
+ time_range = 6.days.ago..Time.now
125
+
126
+ User.group_by_day(:created_at, Time.zone, time_range).count(:created_at)
127
+ # {
128
+ # 2013-05-01 00:00:00 UTC => 0,
129
+ # 2013-05-02 00:00:00 UTC => 1,
130
+ # 2013-05-03 00:00:00 UTC => 0,
131
+ # 2013-05-04 00:00:00 UTC => 0,
132
+ # 2013-05-05 00:00:00 UTC => 1,
133
+ # 2013-05-06 00:00:00 UTC => 0,
134
+ # 2013-05-07 00:00:00 UTC => 0
135
+ # }
136
+ ```
137
+
138
+ Wow, SQL magic!
139
+
140
+ **Note:** Be sure to pass the column name to `count`. Otherwise, you get `1` for empty groups.
141
+
142
+ For the day of the week and hour of the day, just pass `true`.
143
+
144
+ ```ruby
145
+ User.group_by_day_of_week(:created_at, Time.zone, true).count(:created_at)
146
+ # {
147
+ # 0 => 0,
148
+ # 1 => 1,
149
+ # 2 => 0,
150
+ # 3 => 0,
151
+ # 4 => 1,
152
+ # 5 => 0,
153
+ # 6 => 0
154
+ # }
155
+ ```
156
+
106
157
  ## Installation
107
158
 
108
159
  Add this line to your application's Gemfile:
data/lib/groupdate.rb CHANGED
@@ -28,8 +28,9 @@ module Groupdate
28
28
  included do
29
29
  # Field list from
30
30
  # http://www.postgresql.org/docs/9.1/static/functions-datetime.html
31
- fields = %w(second minute hour day week month year day_of_week hour_of_day)
32
- fields.each do |field|
31
+ time_fields = %w(second minute hour day week month year)
32
+ number_fields = %w(day_of_week hour_of_day)
33
+ (time_fields + number_fields).each do |field|
33
34
  self.scope :"group_by_#{field}", lambda {|*args|
34
35
  column = connection.quote_table_name(args[0])
35
36
  time_zone = args[1] || Time.zone || "Etc/UTC"
@@ -83,7 +84,66 @@ module Groupdate
83
84
  raise "Connection adapter not supported: #{connection.adapter_name}"
84
85
  end
85
86
 
86
- group(Groupdate::OrderHack.new(sanitize_sql_array(query), field))
87
+ if args[2] # zeros
88
+ if time_fields.include?(field)
89
+ range = args[2]
90
+ unless range.is_a?(Range)
91
+ raise "Expecting a range"
92
+ end
93
+
94
+ # determine start time
95
+ time = range.first.in_time_zone(time_zone)
96
+ starts_at =
97
+ case field
98
+ when "second"
99
+ time.change(:usec => 0)
100
+ when "minute"
101
+ time.change(:sec => 0)
102
+ when "hour"
103
+ time.change(:min => 0)
104
+ when "day"
105
+ time.beginning_of_day
106
+ when "week"
107
+ time.beginning_of_week(:sunday)
108
+ when "month"
109
+ time.beginning_of_month
110
+ else # year
111
+ time.beginning_of_year
112
+ end
113
+
114
+ series = [starts_at]
115
+
116
+ step = 1.send(field)
117
+
118
+ while range.cover?(series.last + step)
119
+ series << series.last + step
120
+ end
121
+ end
122
+
123
+ derived_table =
124
+ case connection.adapter_name
125
+ when "PostgreSQL"
126
+ case field
127
+ when "day_of_week", "hour_of_day"
128
+ max = field == "day_of_week" ? 6 : 23
129
+ "SELECT generate_series(0, #{max}, 1) AS #{field}"
130
+ else
131
+ sanitize_sql_array(["SELECT (generate_series(CAST(? AS timestamptz) AT TIME ZONE ?, ?, ?) AT TIME ZONE ?) AS #{field}", starts_at, time_zone, series.last, "1 #{field}", time_zone])
132
+ end
133
+ else # MySQL
134
+ case field
135
+ when "day_of_week", "hour_of_day"
136
+ max = field == "day_of_week" ? 6 : 23
137
+ (0..max).map{|i| "SELECT #{i} AS #{field}" }.join(" UNION ")
138
+ else
139
+ sanitize_sql_array([series.map{|i| "SELECT CAST(? AS DATETIME) AS #{field}" }.join(" UNION ")] + series)
140
+ end
141
+ end
142
+
143
+ joins("RIGHT OUTER JOIN (#{derived_table}) groupdate_series ON groupdate_series.#{field} = (#{sanitize_sql_array(query)})").group(Groupdate::OrderHack.new("groupdate_series.#{field}", field))
144
+ else
145
+ group(Groupdate::OrderHack.new(sanitize_sql_array(query), field))
146
+ end
87
147
  }
88
148
  end
89
149
  end
@@ -1,3 +1,3 @@
1
1
  module Groupdate
2
- VERSION = "0.1.4"
2
+ VERSION = "0.1.5"
3
3
  end
@@ -121,6 +121,81 @@ describe Groupdate do
121
121
  it "group_by_day_of_week with time zone" do
122
122
  assert_group_number_tz :day_of_week, "2013-03-03 00:00:00 UTC", 6
123
123
  end
124
+
125
+ describe "returns zeros" do
126
+
127
+ it "group_by_second" do
128
+ assert_zeros :second, "2013-05-01 00:00:01 UTC", ["2013-05-01 00:00:00 UTC", "2013-05-01 00:00:01 UTC", "2013-05-01 00:00:02 UTC"], "2013-05-01 00:00:00.999 UTC", "2013-05-01 00:00:02 UTC"
129
+ end
130
+
131
+ it "group_by_minute" do
132
+ assert_zeros :minute, "2013-05-01 00:01:00 UTC", ["2013-05-01 00:00:00 UTC", "2013-05-01 00:01:00 UTC", "2013-05-01 00:02:00 UTC"], "2013-05-01 00:00:59 UTC", "2013-05-01 00:02:00 UTC"
133
+ end
134
+
135
+ it "group_by_hour" do
136
+ assert_zeros :hour, "2013-05-01 04:01:01 UTC", ["2013-05-01 03:00:00 UTC", "2013-05-01 04:00:00 UTC", "2013-05-01 05:00:00 UTC"], "2013-05-01 03:59:59 UTC", "2013-05-01 05:00:00 UTC"
137
+ end
138
+
139
+ it "group_by_day" do
140
+ assert_zeros :day, "2013-05-01 20:00:00 UTC", ["2013-04-30 00:00:00 UTC", "2013-05-01 00:00:00 UTC", "2013-05-02 00:00:00 UTC"], "2013-04-30 00:00:00 UTC", "2013-05-02 23:59:59 UTC"
141
+ end
142
+
143
+ it "group_by_day with time zone" do
144
+ assert_zeros_tz :day, "2013-05-01 20:00:00 PDT", ["2013-04-30 00:00:00 PDT", "2013-05-01 00:00:00 PDT", "2013-05-02 00:00:00 PDT"], "2013-04-30 00:00:00 PDT", "2013-05-02 23:59:59 PDT"
145
+ end
146
+
147
+ it "group_by_week" do
148
+ assert_zeros :week, "2013-05-01 20:00:00 UTC", ["2013-04-21 00:00:00 UTC", "2013-04-28 00:00:00 UTC", "2013-05-05 00:00:00 UTC"], "2013-04-27 23:59:59 UTC", "2013-05-11 23:59:59 UTC"
149
+ end
150
+
151
+ it "group_by_week with time zone" do
152
+ assert_zeros_tz :week, "2013-05-01 20:00:00 PDT", ["2013-04-21 00:00:00 PDT", "2013-04-28 00:00:00 PDT", "2013-05-05 00:00:00 PDT"], "2013-04-27 23:59:59 PDT", "2013-05-11 23:59:59 PDT"
153
+ end
154
+
155
+ it "group_by_month" do
156
+ assert_zeros :month, "2013-04-16 20:00:00 UTC", ["2013-03-01 00:00:00 UTC", "2013-04-01 00:00:00 UTC", "2013-05-01 00:00:00 UTC"], "2013-03-01 00:00:00 UTC", "2013-05-31 23:59:59 UTC"
157
+ end
158
+
159
+ it "group_by_month with time zone" do
160
+ assert_zeros_tz :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"
161
+ end
162
+
163
+ it "group_by_year" do
164
+ 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"
165
+ end
166
+
167
+ it "group_by_year with time zone" do
168
+ assert_zeros_tz :year, "2013-04-16 20:00:00 PDT", ["2012-01-01 00:00:00 PST", "2013-01-01 00:00:00 PST", "2014-01-01 00:00:00 PST"], "2012-01-01 00:00:00 PST", "2014-12-31 23:59:59 PST"
169
+ end
170
+
171
+ it "group_by_day_of_week" do
172
+ create_user "2013-05-01 00:00:00 UTC"
173
+ expected = {}
174
+ 7.times do |n|
175
+ expected[number_key(n, true)] = n == 3 ? 1 : 0
176
+ end
177
+ assert_equal(expected, User.group_by_day_of_week(:created_at, Time.zone, true).count(:created_at))
178
+ end
179
+
180
+ it "group_by_hour_of_day" do
181
+ create_user "2013-05-01 20:00:00 UTC"
182
+ expected = {}
183
+ 24.times do |n|
184
+ expected[number_key(n, true)] = n == 20 ? 1 : 0
185
+ end
186
+ assert_equal(expected, User.group_by_hour_of_day(:created_at, Time.zone, true).count(:created_at))
187
+ end
188
+
189
+ it "excludes end" do
190
+ create_user "2013-05-02 00:00:00 UTC"
191
+ expected = {
192
+ time_key("2013-05-01 00:00:00 UTC") => 0
193
+ }
194
+ assert_equal(expected, User.group_by_day(:created_at, Time.zone, Time.parse("2013-05-01 00:00:00 UTC")...Time.parse("2013-05-02 00:00:00 UTC")).count(:created_at))
195
+ end
196
+
197
+ end
198
+
124
199
  end
125
200
  end
126
201
 
@@ -128,7 +203,7 @@ describe Groupdate do
128
203
 
129
204
  def assert_group(method, created_at, key, time_zone = nil)
130
205
  create_user created_at
131
- assert_equal(ordered_hash({time_key(key) => 1}), User.send(:"group_by_#{method}", :created_at, time_zone).count)
206
+ assert_equal(ordered_hash({time_key(key) => 1}), User.send(:"group_by_#{method}", :created_at, time_zone).order(method).count)
132
207
  end
133
208
 
134
209
  def assert_group_tz(method, created_at, key)
@@ -137,26 +212,57 @@ describe Groupdate do
137
212
 
138
213
  def assert_group_number(method, created_at, key, time_zone = nil)
139
214
  create_user created_at
140
- assert_equal(ordered_hash({number_key(key) => 1}), User.send(:"group_by_#{method}", :created_at, time_zone).count)
215
+ assert_equal(ordered_hash({number_key(key) => 1}), User.send(:"group_by_#{method}", :created_at, time_zone).order(method).count)
141
216
  end
142
217
 
143
218
  def assert_group_number_tz(method, created_at, key)
144
219
  assert_group_number method, created_at, key, "Pacific Time (US & Canada)"
145
220
  end
146
221
 
147
- def time_key(key)
222
+ def assert_zeros(method, created_at, keys, range_start, range_end, time_zone = nil, java_hack = false)
223
+ create_user created_at
224
+ expected = {}
225
+ keys.each_with_index do |key, i|
226
+ expected[time_key(key, java_hack)] = i == 1 ? 1 : 0
227
+ end
228
+ assert_equal(expected, User.send(:"group_by_#{method}", :created_at, time_zone, Time.parse(range_start)..Time.parse(range_end)).order(method).count(:created_at))
229
+ end
230
+
231
+ def assert_zeros_tz(method, created_at, keys, range_start, range_end)
232
+ assert_zeros method, created_at, keys, range_start, range_end, "Pacific Time (US & Canada)", true
233
+ end
234
+
235
+ def time_key(key, java_hack = false)
148
236
  if RUBY_PLATFORM == "java"
149
- User.connection.adapter_name == "PostgreSQL" ? Time.parse(key).strftime("%Y-%m-%d %H:%M:%S%z")[0..-3] : Time.parse(key).strftime("%Y-%m-%d %H:%M:%S").gsub(/ 00\:00\:00\z/, "")
237
+ if User.connection.adapter_name == "PostgreSQL"
238
+ Time.parse(key).utc.strftime("%Y-%m-%d %H:%M:%S%z")[0..-3]
239
+ elsif java_hack
240
+ Time.parse(key).utc.strftime("%Y-%m-%d %H:%M:%S")
241
+ else
242
+ Time.parse(key).strftime("%Y-%m-%d %H:%M:%S").gsub(/ 00\:00\:00\z/, "")
243
+ end
150
244
  else
151
- User.connection.adapter_name == "PostgreSQL" && ActiveRecord::VERSION::MAJOR == 3 ? Time.parse(key).strftime("%Y-%m-%d %H:%M:%S+00") : Time.parse(key)
245
+ if User.connection.adapter_name == "PostgreSQL" and ActiveRecord::VERSION::MAJOR == 3
246
+ Time.parse(key).utc.strftime("%Y-%m-%d %H:%M:%S+00")
247
+ else
248
+ Time.parse(key)
249
+ end
152
250
  end
153
251
  end
154
252
 
155
- def number_key(key)
253
+ def number_key(key, java_hack = false)
156
254
  if RUBY_PLATFORM == "java"
157
- User.connection.adapter_name == "PostgreSQL" ? key.to_f : key
255
+ if User.connection.adapter_name == "PostgreSQL" and !java_hack
256
+ key.to_f
257
+ else
258
+ key
259
+ end
158
260
  else
159
- User.connection.adapter_name == "PostgreSQL" ? (ActiveRecord::VERSION::MAJOR == 3 ? key.to_s : key.to_f) : key
261
+ if User.connection.adapter_name == "PostgreSQL"
262
+ ActiveRecord::VERSION::MAJOR == 3 ? key.to_s : key.to_f
263
+ else
264
+ key
265
+ end
160
266
  end
161
267
  end
162
268
 
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: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-05-01 00:00:00.000000000 Z
11
+ date: 2013-05-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord