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 +4 -4
- data/README.md +52 -1
- data/lib/groupdate.rb +63 -3
- data/lib/groupdate/version.rb +1 -1
- data/test/groupdate_test.rb +114 -8
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 81ffab786c4714072fad2155f8475839010822af
|
4
|
+
data.tar.gz: 33dd72742440609989c5c61cb267574f8a5611b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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!! **
|
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
|
-
|
32
|
-
|
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
|
-
|
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
|
data/lib/groupdate/version.rb
CHANGED
data/test/groupdate_test.rb
CHANGED
@@ -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
|
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"
|
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"
|
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"
|
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"
|
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
|
+
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-
|
11
|
+
date: 2013-05-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|