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