groupdate 0.1.4 → 0.1.5

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: 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