one-pivot 0.9.3 → 0.9.7
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -4
- data/Gemfile.lock +2 -4
- data/README.md +92 -109
- data/lib/one-pivot.rb +1 -0
- data/lib/one/pivoter.rb +78 -30
- data/test/pivoter_test.rb +65 -53
- metadata +42 -39
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,14 +1,12 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
|
4
|
+
eventmachine (0.12.10)
|
5
5
|
shoulda (2.11.3)
|
6
|
-
yard (0.6.4)
|
7
6
|
|
8
7
|
PLATFORMS
|
9
8
|
ruby
|
10
9
|
|
11
10
|
DEPENDENCIES
|
12
|
-
|
11
|
+
eventmachine (= 0.12.10)
|
13
12
|
shoulda (= 2.11.3)
|
14
|
-
yard (= 0.6.4)
|
data/README.md
CHANGED
@@ -15,130 +15,113 @@ Lets have a look at some examples.
|
|
15
15
|
_Note: there are a few advanced features not demonstrated in the examples below. For example, adding identifiers to pivots or attaching observers to pivot operations. We use these features at 1on1 to cache pivot results for each item. This gives us a big performance boost when the same item participates in multiple pivots during its lifetime... especially when the pivot Proc is an expensive operation. Have a look at the [tests](https://github.com/one-on-one/pivot/tree/master/test) when you want to dig a little deeper._
|
16
16
|
|
17
17
|
##Installation
|
18
|
-
|
19
|
-
<code>
|
20
|
-
gem install one-pivot
|
21
|
-
</code>
|
22
|
-
</pre>
|
18
|
+
gem install one-pivot
|
23
19
|
|
24
20
|
##A simple single pivot
|
25
|
-
|
26
|
-
<code>
|
27
|
-
require 'one-pivot'
|
21
|
+
require 'one-pivot'
|
28
22
|
|
29
|
-
# create the pivot instance
|
30
|
-
pivoter = One::Pivoter.new
|
23
|
+
# create the pivot instance
|
24
|
+
pivoter = One::Pivoter.new
|
31
25
|
|
32
|
-
# create a list of objects to pivot
|
33
|
-
list = [1,2,3,4,5,6,7,8,9]
|
26
|
+
# create a list of objects to pivot
|
27
|
+
list = [1,2,3,4,5,6,7,8,9]
|
34
28
|
|
35
|
-
# run a single pivot
|
36
|
-
# note: the block passed to the pivot method is invoked for each item in the list
|
37
|
-
# note: the result from the block will act as the 'key' in the resulting Hash
|
38
|
-
result = pivoter.pivot(list) {|item| item <= 5}
|
39
|
-
|
40
|
-
# 'result' will be a Hash with the following structure
|
41
|
-
{
|
42
|
-
true => [1,2,3,4,5],
|
43
|
-
false => [6,7,8,9]
|
44
|
-
}
|
45
|
-
</code>
|
46
|
-
</pre>
|
29
|
+
# run a single pivot
|
30
|
+
# note: the block passed to the pivot method is invoked for each item in the list
|
31
|
+
# note: the result from the block will act as the 'key' in the resulting Hash
|
32
|
+
result = pivoter.pivot(list) {|item| item <= 5}
|
47
33
|
|
34
|
+
# 'result' will be a Hash with the following structure
|
35
|
+
{
|
36
|
+
true => [1,2,3,4,5],
|
37
|
+
false => [6,7,8,9]
|
38
|
+
}
|
48
39
|
|
49
40
|
##A simple multi-pivot
|
50
|
-
|
51
|
-
<code>
|
52
|
-
require 'one-pivot'
|
41
|
+
require 'one-pivot'
|
53
42
|
|
54
|
-
# create the pivot instance
|
55
|
-
# note: the multi-pivot delimiter that was specified
|
56
|
-
pivoter = One::Pivoter.new
|
43
|
+
# create the pivot instance
|
44
|
+
# note: the multi-pivot delimiter that was specified
|
45
|
+
pivoter = One::Pivoter.new
|
57
46
|
|
58
|
-
# create a list of objects to pivot
|
59
|
-
list = [1,2,3,4,5,6,7,8,9]
|
47
|
+
# create a list of objects to pivot
|
48
|
+
list = [1,2,3,4,5,6,7,8,9]
|
60
49
|
|
61
|
-
# run several pivots together
|
62
|
-
pivots = []
|
50
|
+
# run several pivots together
|
51
|
+
pivots = []
|
63
52
|
|
64
|
-
pivots << lambda do |i|
|
65
|
-
|
66
|
-
|
67
|
-
end
|
53
|
+
pivots << lambda do |i|
|
54
|
+
key = "less than or equal to 5" if i <= 5
|
55
|
+
key ||= "greater than 5"
|
56
|
+
end
|
68
57
|
|
69
|
-
pivots << lambda do |i|
|
70
|
-
|
71
|
-
|
72
|
-
end
|
58
|
+
pivots << lambda do |i|
|
59
|
+
key = "greater than or equal to 3" if i >= 3
|
60
|
+
key ||= "less than 3"
|
61
|
+
end
|
73
62
|
|
74
|
-
pivots << {:delimiter => " & "}
|
63
|
+
pivots << {:delimiter => " & "}
|
75
64
|
|
76
|
-
result = pivoter.multi_pivot(list, *pivots)
|
65
|
+
result = pivoter.multi_pivot(list, *pivots)
|
77
66
|
|
78
|
-
# 'result' will be a Hash with the following structure
|
79
|
-
{
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
}
|
84
|
-
</code>
|
85
|
-
</pre>
|
67
|
+
# 'result' will be a Hash with the following structure
|
68
|
+
{
|
69
|
+
"less than or equal to 5 & greater than or equal to 3" => [3, 4, 5],
|
70
|
+
"less than or equal to 5 & less than 3" => [1, 2],
|
71
|
+
"greater than 5 & greater than or equal to 3" => [6, 7, 8, 9]
|
72
|
+
}
|
86
73
|
|
87
74
|
##A real world example
|
88
|
-
|
89
|
-
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
# t.
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
99
|
-
# t.
|
100
|
-
#
|
101
|
-
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
#
|
107
|
-
#
|
108
|
-
#
|
109
|
-
#
|
110
|
-
#
|
111
|
-
#
|
112
|
-
#
|
113
|
-
#
|
114
|
-
#
|
115
|
-
#
|
116
|
-
#
|
117
|
-
# {:name => "
|
118
|
-
# {:name => "
|
119
|
-
# {:name => "
|
120
|
-
#
|
121
|
-
#
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
nil => ["Doug"]
|
142
|
-
}
|
143
|
-
</code>
|
144
|
-
</pre>
|
75
|
+
# we'll be working with ActiveRecord objects based on the following schema
|
76
|
+
# ============================================================================
|
77
|
+
# create_table "users", :force => true do |t|
|
78
|
+
# t.string "name"
|
79
|
+
# t.datetime "created_at"
|
80
|
+
# t.datetime "updated_at"
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# create_table "skills", :force => true do |t|
|
84
|
+
# t.string "name"
|
85
|
+
# t.datetime "created_at"
|
86
|
+
# t.datetime "updated_at"
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# create_table "skills_users", :id => false, :force => true do |t|
|
90
|
+
# t.integer "user_id"
|
91
|
+
# t.integer "skill_id"
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# and will seed the datbase with the following data
|
95
|
+
# ============================================================================
|
96
|
+
# ruby = Skill.create(:name => "Ruby")
|
97
|
+
# python = Skill.create(:name => "Python")
|
98
|
+
# php = Skill.create(:name => "PHP")
|
99
|
+
# javascript = Skill.create(:name => "JavaScript")
|
100
|
+
#
|
101
|
+
# users = User.create([
|
102
|
+
# {:name => "Ryan", :skills => [ruby]},
|
103
|
+
# {:name => "Dave", :skills => [php, javascript]},
|
104
|
+
# {:name => "Brett", :skills => [ruby, javascript]},
|
105
|
+
# {:name => "Jay", :skills => [ruby, python, php, javascript]},
|
106
|
+
# {:name => "Doug"}
|
107
|
+
# ])
|
108
|
+
#
|
109
|
+
|
110
|
+
require 'one-pivot'
|
111
|
+
|
112
|
+
pivoter = One::Pivoter.new
|
113
|
+
# note that the pivot block returns an array
|
114
|
+
# each unique value in the arrays returned will become a key in the resulting Hash
|
115
|
+
result = pivoter.pivot(User.all) do |user|
|
116
|
+
user.skills
|
117
|
+
end
|
118
|
+
|
119
|
+
# 'result' will be Hash with the following structure
|
120
|
+
# note: I've simplified the object structure (using names only) for clarity
|
121
|
+
{
|
122
|
+
"Ruby" => ["Brett", "Jay", "Ryan"],
|
123
|
+
"PHP" => ["Dave", "Jay"],
|
124
|
+
"JavaScript" => ["Brett", "Dave", "Jay"],
|
125
|
+
"Python" => ["Jay"],
|
126
|
+
nil => ["Doug"]
|
127
|
+
}
|
data/lib/one-pivot.rb
CHANGED
data/lib/one/pivoter.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
+
require 'rubygems'
|
1
2
|
require 'observer'
|
3
|
+
require 'eventmachine'
|
4
|
+
require 'thread'
|
5
|
+
|
2
6
|
|
3
7
|
# Namespace module for One on One Marketing.
|
4
8
|
module One
|
@@ -16,44 +20,77 @@ module One
|
|
16
20
|
# # the result will be a Hash with the following structure
|
17
21
|
# {
|
18
22
|
# true=>[1, 2, 3, 4, 5],
|
19
|
-
# false=>[6, 7, 8, 9]
|
23
|
+
# false=>[6, 7, 8, 9]
|
20
24
|
# }
|
21
25
|
#
|
22
26
|
# @param [Array<Object>] list The list to pivot
|
23
27
|
# @param [optional, Hash] options Options to pivot with
|
24
|
-
# @option options [Object] :identifier A name that uniquely identifies the pivot operation
|
25
|
-
# @yield [item] The block passed to pivot will be called for each item in the list
|
28
|
+
# @option options [Object] :identifier A name that uniquely identifies the pivot operation
|
29
|
+
# @yield [item] The block passed to pivot will be called for each item in the list
|
26
30
|
# @yieldparam [Object] item An item in the list
|
27
31
|
# @yieldreturn [Object] The value returned from the pivot block will serve as the key in the pivot results
|
28
32
|
# @return [Hash] The pivoted results
|
29
33
|
def pivot(list, options={}, &block)
|
30
34
|
pivoted = {}
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
35
|
+
semaphore = Mutex.new
|
36
|
+
|
37
|
+
lists = list.each_slice(chunk_size(list)).to_a
|
38
|
+
loops = 0
|
39
|
+
|
40
|
+
reactor_running = EM.reactor_running?
|
41
|
+
|
42
|
+
work = Proc.new do
|
43
|
+
lists.each do |sub_list|
|
44
|
+
|
45
|
+
pivot_operation = Proc.new do
|
46
|
+
sub_list.each do |item|
|
47
|
+
# potential long running operation with blocking IO
|
48
|
+
value = yield(item)
|
49
|
+
|
50
|
+
# notify observers that a pivot block was just called
|
51
|
+
identifier = options[:identifier] || "#{item.hash}:#{block.hash}"
|
52
|
+
changed
|
53
|
+
# potential long running operation with blocking IO
|
54
|
+
notify_observers(identifier, item, value)
|
55
|
+
|
56
|
+
semaphore.synchronize {
|
57
|
+
if value.is_a?(Array)
|
58
|
+
if value.empty?
|
59
|
+
pivoted[nil] ||= []
|
60
|
+
pivoted[nil] << item
|
61
|
+
else
|
62
|
+
value.each do |val|
|
63
|
+
pivoted[val] ||= []
|
64
|
+
pivoted[val] << item
|
65
|
+
end
|
66
|
+
end
|
67
|
+
else
|
68
|
+
pivoted[value] ||= []
|
69
|
+
pivoted[value] << item
|
70
|
+
end
|
71
|
+
}
|
47
72
|
end
|
48
73
|
end
|
49
|
-
|
50
|
-
|
51
|
-
|
74
|
+
|
75
|
+
pivot_callback = Proc.new do
|
76
|
+
semaphore.synchronize {
|
77
|
+
loops += 1
|
78
|
+
EM.stop if loops == lists.length && !reactor_running
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
EM.defer(pivot_operation, pivot_callback)
|
52
83
|
end
|
53
84
|
end
|
54
85
|
|
86
|
+
if reactor_running
|
87
|
+
work.call
|
88
|
+
else
|
89
|
+
EM.run &work
|
90
|
+
end
|
91
|
+
|
55
92
|
pivoted
|
56
|
-
end
|
93
|
+
end
|
57
94
|
|
58
95
|
# Runs multiple pivots against a list of Objects.
|
59
96
|
#
|
@@ -70,17 +107,17 @@ module One
|
|
70
107
|
# key = "greater than or equal to 3" if i >= 3
|
71
108
|
# key ||= "less than 3"
|
72
109
|
# end
|
73
|
-
#
|
110
|
+
#
|
74
111
|
# # note the last pivot is an options Hash
|
75
112
|
# pivots << {:delimiter => " & "}
|
76
|
-
#
|
113
|
+
#
|
77
114
|
# pivoter = One::Pivot.new
|
78
|
-
# result = pivoter.multi_pivot(list, *pivots)
|
79
|
-
#
|
115
|
+
# result = pivoter.multi_pivot(list, *pivots)
|
116
|
+
#
|
80
117
|
# # the result will be a Hash with the following structure
|
81
118
|
# {
|
82
|
-
# "less than or equal to 5 & greater than or equal to 3" => [3, 4, 5],
|
83
|
-
# "less than or equal to 5 & less than 3" => [1, 2],
|
119
|
+
# "less than or equal to 5 & greater than or equal to 3" => [3, 4, 5],
|
120
|
+
# "less than or equal to 5 & less than 3" => [1, 2],
|
84
121
|
# "greater than 5 & greater than or equal to 3" => [6, 7, 8, 9]
|
85
122
|
# }
|
86
123
|
#
|
@@ -99,7 +136,7 @@ module One
|
|
99
136
|
|
100
137
|
while pivots.length > 0
|
101
138
|
p = pivots.shift
|
102
|
-
|
139
|
+
|
103
140
|
# handle the case where the pivots are One::Pivot objects
|
104
141
|
pivot_options = {}
|
105
142
|
if p.is_a?(One::Pivot)
|
@@ -134,6 +171,17 @@ module One
|
|
134
171
|
key.to_s
|
135
172
|
end
|
136
173
|
|
174
|
+
def chunk_size(list)
|
175
|
+
len = list.length
|
176
|
+
case len
|
177
|
+
when 0..100 then len
|
178
|
+
when 100..1000 then len / 2
|
179
|
+
when 1000..10000 then len / 4
|
180
|
+
when 10000..100000 then len / 8
|
181
|
+
else len / 16
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
137
185
|
end
|
138
186
|
end
|
139
187
|
|
data/test/pivoter_test.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
2
2
|
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'one', 'pivot'))
|
3
3
|
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'one', 'pivoter'))
|
4
|
+
require 'eventmachine'
|
4
5
|
|
5
6
|
class PivotTest < Test::Unit::TestCase
|
6
7
|
|
@@ -13,33 +14,44 @@ class PivotTest < Test::Unit::TestCase
|
|
13
14
|
list = [1,2,3,4,5,6,7,8,9]
|
14
15
|
result = @pivoter.pivot(list) {|item| item <= 5}
|
15
16
|
assert_equal [6,7,8,9], result[false]
|
16
|
-
assert_equal [1,2,3,4,5], result[true]
|
17
|
+
assert_equal [1,2,3,4,5], result[true]
|
17
18
|
end
|
18
19
|
|
19
|
-
should "
|
20
|
+
should "pivot inside of an existing EventMachine reactor" do
|
21
|
+
EM.run do
|
22
|
+
list = [1,2,3,4,5,6,7,8,9]
|
23
|
+
result = @pivoter.pivot(list) {|item| item <= 5}
|
24
|
+
sleep(0.01) # allow enough time for the pivot to complete
|
25
|
+
assert_equal [6,7,8,9], result[false]
|
26
|
+
assert_equal [1,2,3,4,5], result[true]
|
27
|
+
EM.stop
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
should "multi pivot a simple array properly" do
|
20
32
|
list = [1,2,3,4,5,6,7,8,9]
|
21
33
|
pivots = []
|
22
|
-
|
34
|
+
|
23
35
|
pivots << lambda do |i|
|
24
36
|
key = "less than or equal to 5" if i <= 5
|
25
37
|
key ||= "greater than 5"
|
26
38
|
end
|
27
|
-
|
39
|
+
|
28
40
|
pivots << lambda do |i|
|
29
41
|
key = "greater than or equal to 3" if i >= 3
|
30
42
|
key ||= "less than 3"
|
31
43
|
end
|
32
|
-
|
44
|
+
|
33
45
|
pivots << {:delimiter => " & "}
|
34
46
|
|
35
47
|
result = @pivoter.multi_pivot(list, *pivots)
|
36
|
-
|
48
|
+
|
37
49
|
# the result will be a Hash with the following structure:
|
38
50
|
hash = {
|
39
|
-
"less than or equal to 5 & greater than or equal to 3" => [3, 4, 5],
|
40
|
-
"less than or equal to 5 & less than 3" => [1, 2],
|
51
|
+
"less than or equal to 5 & greater than or equal to 3" => [3, 4, 5],
|
52
|
+
"less than or equal to 5 & less than 3" => [1, 2],
|
41
53
|
"greater than 5 & greater than or equal to 3" => [6, 7, 8, 9]
|
42
|
-
}
|
54
|
+
}
|
43
55
|
|
44
56
|
assert_equal hash, result
|
45
57
|
end
|
@@ -47,38 +59,38 @@ class PivotTest < Test::Unit::TestCase
|
|
47
59
|
should "support both procs and One::Pivot objects with multi_pivot" do
|
48
60
|
list = [1,2,3,4,5,6,7,8,9]
|
49
61
|
pivots = []
|
50
|
-
|
62
|
+
|
51
63
|
pivots << One::Pivot.new("comparison to 5") do |i|
|
52
64
|
key = "less than or equal to 5" if i <= 5
|
53
65
|
key ||= "greater than 5"
|
54
66
|
end
|
55
|
-
|
67
|
+
|
56
68
|
pivots << One::Pivot.new("comparison to 3") do |i|
|
57
69
|
key = "greater than or equal to 3" if i >= 3
|
58
70
|
key ||= "less than 3"
|
59
71
|
end
|
60
|
-
|
72
|
+
|
61
73
|
pivots << {:delimiter => " & "}
|
62
74
|
|
63
75
|
result = @pivoter.multi_pivot(list, *pivots)
|
64
|
-
|
76
|
+
|
65
77
|
# the result will be a Hash with the following structure:
|
66
78
|
hash = {
|
67
|
-
"less than or equal to 5 & greater than or equal to 3" => [3, 4, 5],
|
68
|
-
"less than or equal to 5 & less than 3" => [1, 2],
|
79
|
+
"less than or equal to 5 & greater than or equal to 3" => [3, 4, 5],
|
80
|
+
"less than or equal to 5 & less than 3" => [1, 2],
|
69
81
|
"greater than 5 & greater than or equal to 3" => [6, 7, 8, 9]
|
70
|
-
}
|
82
|
+
}
|
71
83
|
|
72
84
|
assert_equal hash, result
|
73
85
|
end
|
74
86
|
|
75
87
|
should "support pivot observers (for example: to perform caching)" do
|
76
88
|
# Pivot operations can be expensive.
|
77
|
-
#
|
89
|
+
#
|
78
90
|
# Demonstrate how you can use the observer pattern to cache
|
79
91
|
# pivot results per item to save time on subsequent pivots for the same item.
|
80
|
-
#
|
81
|
-
# This strategy can be helpful if each item remains in memory for a period of time
|
92
|
+
#
|
93
|
+
# This strategy can be helpful if each item remains in memory for a period of time
|
82
94
|
# that spans its participation in several pivot calls
|
83
95
|
|
84
96
|
# extend Fixnum so we can cache pivot results
|
@@ -99,11 +111,11 @@ class PivotTest < Test::Unit::TestCase
|
|
99
111
|
# this method gets invoked by the pivot object whenever a pivot block is executed
|
100
112
|
def update(identifier, item, value)
|
101
113
|
item.pivot_cache[identifier] = value
|
102
|
-
end
|
103
|
-
end
|
114
|
+
end
|
115
|
+
end
|
104
116
|
|
105
117
|
observer = PivotObserver.new(@pivoter)
|
106
|
-
|
118
|
+
|
107
119
|
list = [1,2,3,4,5,6,7,8,9]
|
108
120
|
result = @pivoter.pivot(list, :identifier => :less_than_5) do |item|
|
109
121
|
if item.pivot_cache.has_key?(:less_than_5)
|
@@ -116,8 +128,8 @@ class PivotTest < Test::Unit::TestCase
|
|
116
128
|
list.each do |item|
|
117
129
|
assert item.pivot_cache.has_key?(:less_than_5)
|
118
130
|
end
|
119
|
-
|
120
|
-
# pivot again but this time use the pivot cache stored on each item
|
131
|
+
|
132
|
+
# pivot again but this time use the pivot cache stored on each item
|
121
133
|
cached_result = @pivoter.pivot(list, :identifier => :less_than_5) do |item|
|
122
134
|
if item.pivot_cache.has_key?(:less_than_5)
|
123
135
|
item.pivot_cache[:less_than_5]
|
@@ -131,11 +143,11 @@ class PivotTest < Test::Unit::TestCase
|
|
131
143
|
|
132
144
|
should "support pivot observers when using multi_pivot (for example: to perform caching)" do
|
133
145
|
# Pivot operations can be expensive.
|
134
|
-
#
|
146
|
+
#
|
135
147
|
# Demonstrate how you can use the observer pattern to cache
|
136
148
|
# pivot results per item to save time on subsequent pivots for the same item.
|
137
|
-
#
|
138
|
-
# This strategy can be helpful if each item remains in memory for a period of time
|
149
|
+
#
|
150
|
+
# This strategy can be helpful if each item remains in memory for a period of time
|
139
151
|
# that spans its participation in several pivot calls
|
140
152
|
|
141
153
|
# extend Fixnum so we can cache pivot results
|
@@ -156,44 +168,44 @@ class PivotTest < Test::Unit::TestCase
|
|
156
168
|
# this method gets invoked by the pivot object whenever a pivot block is executed
|
157
169
|
def update(identifier, item, value)
|
158
170
|
item.pivot_cache[identifier] = value
|
159
|
-
end
|
160
|
-
end
|
171
|
+
end
|
172
|
+
end
|
161
173
|
|
162
174
|
observer = PivotObserver.new(@pivoter)
|
163
|
-
|
175
|
+
|
164
176
|
list = [1,2,3,4,5,6,7,8,9]
|
165
177
|
|
166
178
|
pivots = []
|
167
|
-
|
179
|
+
|
168
180
|
pivots << One::Pivot.new("comparison to 5") do |i|
|
169
181
|
key = "less than or equal to 5" if i <= 5
|
170
182
|
key ||= "greater than 5"
|
171
183
|
end
|
172
|
-
|
184
|
+
|
173
185
|
pivots << One::Pivot.new("comparison to 3") do |i|
|
174
186
|
key = "greater than or equal to 3" if i >= 3
|
175
187
|
key ||= "less than 3"
|
176
188
|
end
|
177
189
|
|
178
|
-
result = @pivoter.multi_pivot(list, *pivots)
|
190
|
+
result = @pivoter.multi_pivot(list, *pivots)
|
179
191
|
|
180
192
|
list.each do |item|
|
181
193
|
assert item.pivot_cache.has_key?("comparison to 5")
|
182
194
|
assert item.pivot_cache.has_key?("comparison to 3")
|
183
195
|
end
|
184
|
-
|
185
|
-
# pivot again but this time use the pivot cache stored on each item
|
196
|
+
|
197
|
+
# pivot again but this time use the pivot cache stored on each item
|
186
198
|
pivots = []
|
187
|
-
|
199
|
+
|
188
200
|
pivots << One::Pivot.new("comparison to 5") do |i|
|
189
201
|
i.pivot_cache["comparison to 5"]
|
190
202
|
end
|
191
|
-
|
203
|
+
|
192
204
|
pivots << One::Pivot.new("comparison to 3") do |i|
|
193
205
|
i.pivot_cache["comparison to 3"]
|
194
206
|
end
|
195
207
|
|
196
|
-
cached_result = @pivoter.multi_pivot(list, *pivots)
|
208
|
+
cached_result = @pivoter.multi_pivot(list, *pivots)
|
197
209
|
assert_equal result, cached_result
|
198
210
|
end
|
199
211
|
|
@@ -210,16 +222,16 @@ class PivotTest < Test::Unit::TestCase
|
|
210
222
|
|
211
223
|
# Frank gets Read
|
212
224
|
users[0][:roles] << "Read"
|
213
|
-
|
225
|
+
|
214
226
|
# Joe gets Write
|
215
|
-
users[1][:roles] << "Write"
|
227
|
+
users[1][:roles] << "Write"
|
216
228
|
|
217
229
|
# John gets Read & Write
|
218
|
-
users[2][:roles] << "Read"
|
230
|
+
users[2][:roles] << "Read"
|
219
231
|
users[2][:roles] << "Write"
|
220
232
|
|
221
233
|
# Sally gets no roles
|
222
|
-
|
234
|
+
|
223
235
|
@users = users
|
224
236
|
end
|
225
237
|
|
@@ -229,15 +241,15 @@ class PivotTest < Test::Unit::TestCase
|
|
229
241
|
# this is what the resulting hash should look like:
|
230
242
|
hash = {
|
231
243
|
nil => [
|
232
|
-
{:name=>"Sally", :gender => "F", :roles=>[]}],
|
244
|
+
{:name=>"Sally", :gender => "F", :roles=>[]}],
|
233
245
|
"Read" => [
|
234
|
-
{:name=>"Frank", :gender => "M", :roles=>["Read"]},
|
235
|
-
{:name=>"John", :gender => "M", :roles=>["Read", "Write"]}],
|
246
|
+
{:name=>"Frank", :gender => "M", :roles=>["Read"]},
|
247
|
+
{:name=>"John", :gender => "M", :roles=>["Read", "Write"]}],
|
236
248
|
"Write" => [
|
237
|
-
{:name=>"Joe", :gender => "M", :roles=>["Write"]},
|
249
|
+
{:name=>"Joe", :gender => "M", :roles=>["Write"]},
|
238
250
|
{:name=>"John", :gender => "M", :roles=>["Read", "Write"]}]
|
239
251
|
}
|
240
|
-
|
252
|
+
|
241
253
|
assert_equal hash, result
|
242
254
|
end
|
243
255
|
|
@@ -245,21 +257,21 @@ class PivotTest < Test::Unit::TestCase
|
|
245
257
|
pivots = []
|
246
258
|
pivots << lambda {|user| user[:gender]}
|
247
259
|
pivots << lambda {|user| user[:roles]}
|
248
|
-
|
260
|
+
|
249
261
|
result = @pivoter.multi_pivot(@users, *pivots)
|
250
262
|
|
251
263
|
# this is what the resulting hash should look like
|
252
264
|
hash = {
|
253
265
|
"M[PIVOT]Write" => [
|
254
|
-
{:name=>"Joe", :gender=>"M", :roles=>["Write"]},
|
255
|
-
{:name=>"John", :gender=>"M", :roles=>["Read", "Write"]}],
|
266
|
+
{:name=>"Joe", :gender=>"M", :roles=>["Write"]},
|
267
|
+
{:name=>"John", :gender=>"M", :roles=>["Read", "Write"]}],
|
256
268
|
"M[PIVOT]Read" => [
|
257
|
-
{:name=>"Frank", :gender=>"M", :roles=>["Read"]},
|
258
|
-
{:name=>"John", :gender=>"M", :roles=>["Read", "Write"]}],
|
269
|
+
{:name=>"Frank", :gender=>"M", :roles=>["Read"]},
|
270
|
+
{:name=>"John", :gender=>"M", :roles=>["Read", "Write"]}],
|
259
271
|
"F[PIVOT]nil" => [
|
260
272
|
{:name=>"Sally", :gender=>"F", :roles=>[]}]
|
261
273
|
}
|
262
|
-
|
274
|
+
|
263
275
|
assert_equal hash, result
|
264
276
|
end
|
265
277
|
|
metadata
CHANGED
@@ -1,40 +1,47 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: one-pivot
|
3
|
-
version: !ruby/object:Gem::Version
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.7
|
4
5
|
prerelease:
|
5
|
-
version: 0.9.3
|
6
6
|
platform: ruby
|
7
|
-
authors:
|
7
|
+
authors:
|
8
8
|
- Nathan Hopkins
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
12
|
+
date: 2011-11-08 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: eventmachine
|
16
|
+
requirement: &2161266960 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - =
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.12.10
|
22
|
+
type: :runtime
|
18
23
|
prerelease: false
|
19
|
-
|
24
|
+
version_requirements: *2161266960
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: shoulda
|
27
|
+
requirement: &2161266360 !ruby/object:Gem::Requirement
|
20
28
|
none: false
|
21
|
-
requirements:
|
22
|
-
- -
|
23
|
-
- !ruby/object:Gem::Version
|
29
|
+
requirements:
|
30
|
+
- - =
|
31
|
+
- !ruby/object:Gem::Version
|
24
32
|
version: 2.11.3
|
25
33
|
type: :development
|
26
|
-
|
27
|
-
|
28
|
-
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2161266360
|
36
|
+
description: ! " A simple and intuitive way to perform powerful data mining on
|
37
|
+
lists of objects.\n Think of it as MapReduce for mortals.\n"
|
38
|
+
email:
|
29
39
|
- natehop@gmail.com
|
30
40
|
- natehop@1on1.com
|
31
41
|
executables: []
|
32
|
-
|
33
42
|
extensions: []
|
34
|
-
|
35
43
|
extra_rdoc_files: []
|
36
|
-
|
37
|
-
files:
|
44
|
+
files:
|
38
45
|
- lib/one/pivot.rb
|
39
46
|
- lib/one/pivoter.rb
|
40
47
|
- lib/one-pivot.rb
|
@@ -45,33 +52,29 @@ files:
|
|
45
52
|
- test/pivot_test.rb
|
46
53
|
- test/pivoter_test.rb
|
47
54
|
- test/test_helper.rb
|
48
|
-
has_rdoc: true
|
49
55
|
homepage: https://github.com/one-on-one/pivot
|
50
|
-
licenses:
|
56
|
+
licenses:
|
51
57
|
- MIT
|
52
58
|
post_install_message:
|
53
59
|
rdoc_options: []
|
54
|
-
|
55
|
-
require_paths:
|
60
|
+
require_paths:
|
56
61
|
- lib
|
57
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
63
|
none: false
|
59
|
-
requirements:
|
60
|
-
- -
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version:
|
63
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
69
|
none: false
|
65
|
-
requirements:
|
66
|
-
- -
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version:
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
69
74
|
requirements: []
|
70
|
-
|
71
75
|
rubyforge_project:
|
72
|
-
rubygems_version: 1.
|
76
|
+
rubygems_version: 1.8.10
|
73
77
|
signing_key:
|
74
78
|
specification_version: 3
|
75
|
-
summary:
|
79
|
+
summary: Enumerable#group_by on steroids with an extra dash of awesome.
|
76
80
|
test_files: []
|
77
|
-
|