one-pivot 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +7 -0
- data/Gemfile.lock +14 -0
- data/README.md +137 -0
- data/Rakefile +10 -0
- data/lib/one-pivot.rb +2 -0
- data/lib/one/pivot.rb +21 -0
- data/lib/one/pivoter.rb +139 -0
- data/test/pivot_test.rb +29 -0
- data/test/pivoter_test.rb +268 -0
- data/test/test_helper.rb +2 -0
- metadata +77 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/README.md
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
#[1on1](http://1on1.com/) one-pivot GEM
|
2
|
+
|
3
|
+
##Overview
|
4
|
+
The **one-pivot** GEM provides a simple way to mine data from a list of objects. There are no constraints on the types of objects that you can pivot. You can pivot anything from a list of numbers to a list of ActiveRecord objects to anything in between.
|
5
|
+
|
6
|
+
**`One::Pivot`** exposes two methods of importance.
|
7
|
+
|
8
|
+
* **`pivot`** - runs a single pivot
|
9
|
+
* **`multi-pivot`** - stacks multiple pivots into a single result
|
10
|
+
|
11
|
+
A **`pivot`** is simply a Ruby block (or Proc) that executes for each item in the list. The result returned from this block then serves as the key in the resulting Hash.
|
12
|
+
|
13
|
+
Lets have a look at some examples.
|
14
|
+
|
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
|
+
|
17
|
+
##A simple single pivot
|
18
|
+
<pre>
|
19
|
+
<code>
|
20
|
+
require 'one-pivot'
|
21
|
+
|
22
|
+
# create the pivot instance
|
23
|
+
pivoter = One::Pivot.new
|
24
|
+
|
25
|
+
# create a list of objects to pivot
|
26
|
+
list = [1,2,3,4,5,6,7,8,9]
|
27
|
+
|
28
|
+
# run a single pivot
|
29
|
+
# note: the block passed to the pivot method is invoked for each item in the list
|
30
|
+
# note: the result from the block will act as the 'key' in the resulting Hash
|
31
|
+
result = pivoter.pivot {|item| item <= 5}
|
32
|
+
|
33
|
+
# 'result' will be a Hash with the following structure
|
34
|
+
{
|
35
|
+
true => [1,2,3,4,5],
|
36
|
+
false => [6,7,8,9]
|
37
|
+
}
|
38
|
+
</code>
|
39
|
+
</pre>
|
40
|
+
|
41
|
+
|
42
|
+
##A simple multi-pivot
|
43
|
+
<pre>
|
44
|
+
<code>
|
45
|
+
require 'one-pivot'
|
46
|
+
|
47
|
+
# create the pivot instance
|
48
|
+
# note: the multi-pivot delimiter that was specified
|
49
|
+
pivoter = One::Pivot.new
|
50
|
+
|
51
|
+
# create a list of objects to pivot
|
52
|
+
list = [1,2,3,4,5,6,7,8,9]
|
53
|
+
|
54
|
+
# run several pivots together
|
55
|
+
pivots = []
|
56
|
+
|
57
|
+
pivots << lambda do |i|
|
58
|
+
key = "less than or equal to 5" if i <= 5
|
59
|
+
key ||= "greater than 5"
|
60
|
+
end
|
61
|
+
|
62
|
+
pivots << lambda do |i|
|
63
|
+
key = "greater than or equal to 3" if i >= 3
|
64
|
+
key ||= "less than 3"
|
65
|
+
end
|
66
|
+
|
67
|
+
pivots << {:delimiter => " & "}
|
68
|
+
|
69
|
+
result = pivoter.multi_pivot(list, *pivots)
|
70
|
+
|
71
|
+
# 'result' will be a Hash with the following structure
|
72
|
+
{
|
73
|
+
"less than or equal to 5 & greater than or equal to 3" => [3, 4, 5],
|
74
|
+
"less than or equal to 5 & less than 3" => [1, 2],
|
75
|
+
"greater than 5 & greater than or equal to 3" => [6, 7, 8, 9]
|
76
|
+
}
|
77
|
+
</code>
|
78
|
+
</pre>
|
79
|
+
|
80
|
+
##A real world example
|
81
|
+
<pre>
|
82
|
+
<code>
|
83
|
+
# we'll be working with ActiveRecord objects based on the following schema
|
84
|
+
# ============================================================================
|
85
|
+
# create_table "users", :force => true do |t|
|
86
|
+
# t.string "name"
|
87
|
+
# t.datetime "created_at"
|
88
|
+
# t.datetime "updated_at"
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# create_table "skills", :force => true do |t|
|
92
|
+
# t.string "name"
|
93
|
+
# t.datetime "created_at"
|
94
|
+
# t.datetime "updated_at"
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# create_table "skills_users", :id => false, :force => true do |t|
|
98
|
+
# t.integer "user_id"
|
99
|
+
# t.integer "skill_id"
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# and will seed the datbase with the following data
|
103
|
+
# ============================================================================
|
104
|
+
# ruby = Skill.create(:name => "Ruby")
|
105
|
+
# python = Skill.create(:name => "Python")
|
106
|
+
# php = Skill.create(:name => "PHP")
|
107
|
+
# javascript = Skill.create(:name => "JavaScript")
|
108
|
+
#
|
109
|
+
# users = User.create([
|
110
|
+
# {:name => "Ryan", :skills => [ruby]},
|
111
|
+
# {:name => "Dave", :skills => [php, javascript]},
|
112
|
+
# {:name => "Brett", :skills => [ruby, javascript]},
|
113
|
+
# {:name => "Jay", :skills => [ruby, python, php, javascript]},
|
114
|
+
# {:name => "Doug"}
|
115
|
+
# ])
|
116
|
+
#
|
117
|
+
|
118
|
+
require 'one-pivot'
|
119
|
+
|
120
|
+
pivoter = One::Pivot.new
|
121
|
+
# note that the pivot block returns an array
|
122
|
+
# each unique value in the arrays returned will become a key in the resulting Hash
|
123
|
+
result = pivoter.pivot(User.all) do |user|
|
124
|
+
user.skills
|
125
|
+
end
|
126
|
+
|
127
|
+
# 'result' will be Hash with the following structure
|
128
|
+
# note: I've simplified the object structure (using names only) for clarity
|
129
|
+
{
|
130
|
+
"Ruby" => ["Brett", "Jay", "Ryan"],
|
131
|
+
"PHP" => ["Dave", "Jay"],
|
132
|
+
"JavaScript" => ["Brett", "Dave", "Jay"],
|
133
|
+
"Python" => ["Jay"],
|
134
|
+
nil => ["Doug"]
|
135
|
+
}
|
136
|
+
</code>
|
137
|
+
</pre>
|
data/Rakefile
ADDED
data/lib/one-pivot.rb
ADDED
data/lib/one/pivot.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module One
|
2
|
+
|
3
|
+
# Simple class to hold meta information about a pivot.
|
4
|
+
# This class was created to support invoking One::Pivot#multi_pivot with identifiers for each pivot.
|
5
|
+
class Pivot
|
6
|
+
attr_reader :identifier
|
7
|
+
attr_reader :pivot_proc
|
8
|
+
|
9
|
+
# Constructor.
|
10
|
+
# param [String, Symbol] identifier The name of the pivot
|
11
|
+
# @yield [item] This block will be called for each item in the list when pivot is invoked
|
12
|
+
# @yieldparam [Object] item An item in the list
|
13
|
+
# @yieldreturn [Object] The value returned from the pivot block will serve as the key in the pivot results
|
14
|
+
def initialize(identifier, &block)
|
15
|
+
raise LocalJumpError.new("no block given") unless block_given?
|
16
|
+
@identifier = identifier
|
17
|
+
@pivot_proc = block
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
data/lib/one/pivoter.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'observer'
|
2
|
+
|
3
|
+
# Namespace module for One on One Marketing.
|
4
|
+
module One
|
5
|
+
|
6
|
+
# Class that can be used for mining data from lists of objects.
|
7
|
+
class Pivoter
|
8
|
+
include Observable
|
9
|
+
|
10
|
+
# Pivots a list of Objects grouping them into an organized Hash.
|
11
|
+
#
|
12
|
+
# @example Pivot a list of numbers into 2 groups, those less than or equal to 5 and those greater than 5
|
13
|
+
# list = [1,2,3,4,5,6,7,8,9]
|
14
|
+
# result = pivot(list) {|num| num <=5 }
|
15
|
+
#
|
16
|
+
# # the result will be a Hash with the following structure
|
17
|
+
# {
|
18
|
+
# true=>[1, 2, 3, 4, 5],
|
19
|
+
# false=>[6, 7, 8, 9]
|
20
|
+
# }
|
21
|
+
#
|
22
|
+
# @param [Array<Object>] list The list to pivot
|
23
|
+
# @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
|
26
|
+
# @yieldparam [Object] item An item in the list
|
27
|
+
# @yieldreturn [Object] The value returned from the pivot block will serve as the key in the pivot results
|
28
|
+
# @return [Hash] The pivoted results
|
29
|
+
def pivot(list, options={}, &block)
|
30
|
+
pivoted = {}
|
31
|
+
list.each do |item|
|
32
|
+
value = yield(item)
|
33
|
+
|
34
|
+
# notify observers that a pivot block was just called
|
35
|
+
identifier = options[:identifier] || "#{item.hash}:#{block.hash}"
|
36
|
+
changed
|
37
|
+
notify_observers(identifier, item, value)
|
38
|
+
|
39
|
+
if value.is_a?(Array)
|
40
|
+
if value.empty?
|
41
|
+
pivoted[nil] ||= []
|
42
|
+
pivoted[nil] << item
|
43
|
+
else
|
44
|
+
value.each do |val|
|
45
|
+
pivoted[val] ||= []
|
46
|
+
pivoted[val] << item
|
47
|
+
end
|
48
|
+
end
|
49
|
+
else
|
50
|
+
pivoted[value] ||= []
|
51
|
+
pivoted[value] << item
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
pivoted
|
56
|
+
end
|
57
|
+
|
58
|
+
# Runs multiple pivots against a list of Objects.
|
59
|
+
#
|
60
|
+
# @example Multi-pivot a list of numbers
|
61
|
+
# list = [1,2,3,4,5,6,7,8,9]
|
62
|
+
# pivots = []
|
63
|
+
#
|
64
|
+
# pivots << lambda do |i|
|
65
|
+
# key = "less than or equal to 5" if i <= 5
|
66
|
+
# key ||= "greater than 5"
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# pivots << lambda do |i|
|
70
|
+
# key = "greater than or equal to 3" if i >= 3
|
71
|
+
# key ||= "less than 3"
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# # note the last pivot is an options Hash
|
75
|
+
# pivots << {:delimiter => " & "}
|
76
|
+
#
|
77
|
+
# pivoter = One::Pivot.new
|
78
|
+
# result = pivoter.multi_pivot(list, *pivots)
|
79
|
+
#
|
80
|
+
# # the result will be a Hash with the following structure
|
81
|
+
# {
|
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],
|
84
|
+
# "greater than 5 & greater than or equal to 3" => [6, 7, 8, 9]
|
85
|
+
# }
|
86
|
+
#
|
87
|
+
# @param [Array<Object>] list The list to run the pivots against
|
88
|
+
# @param [Array<Proc, One::Pivot>] pivots An argument list that accepts N number of pivots.<br />
|
89
|
+
# The last pivot in the list can be an options Hash for the multi_pivot operation.<br />
|
90
|
+
# Supported options are:<br />
|
91
|
+
# * :delimiter, The delimiter to use between pivot keys. Defaults to '[PIVOT]']
|
92
|
+
# @return [Hash] The pivoted results
|
93
|
+
def multi_pivot(list, *pivots)
|
94
|
+
options = pivots.pop if pivots.last.is_a?(Hash)
|
95
|
+
options ||= {}
|
96
|
+
delimiter = options[:delimiter] || "[PIVOT]"
|
97
|
+
pivoted = nil
|
98
|
+
pass = 0
|
99
|
+
|
100
|
+
while pivots.length > 0
|
101
|
+
p = pivots.shift
|
102
|
+
|
103
|
+
# handle the case where the pivots are One::Pivot objects
|
104
|
+
pivot_options = {}
|
105
|
+
if p.is_a?(One::Pivot)
|
106
|
+
pivot_options[:identifier] = p.identifier
|
107
|
+
p = p.pivot_proc
|
108
|
+
end
|
109
|
+
|
110
|
+
if pass == 0
|
111
|
+
pivoted = pivot(list, pivot_options, &p)
|
112
|
+
else
|
113
|
+
new_pivoted = {}
|
114
|
+
pivoted.each do |old_key, old_list|
|
115
|
+
tmp_pivoted = pivot(old_list, pivot_options, &p)
|
116
|
+
tmp_pivoted.each do |key, list|
|
117
|
+
new_key = "#{safe_key(old_key)}#{delimiter}#{safe_key(key)}"
|
118
|
+
new_pivoted[new_key] = list
|
119
|
+
end
|
120
|
+
end
|
121
|
+
pivoted = new_pivoted
|
122
|
+
end
|
123
|
+
|
124
|
+
pass += 1
|
125
|
+
end
|
126
|
+
|
127
|
+
pivoted
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def safe_key(key)
|
133
|
+
key = "nil" if key.to_s.strip.empty?
|
134
|
+
key.to_s
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
data/test/pivot_test.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'one', 'pivot'))
|
3
|
+
|
4
|
+
class PivotTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
context "A Pivot instance" do
|
7
|
+
|
8
|
+
should "expect an identifier passed to construct" do
|
9
|
+
assert_raise ArgumentError do
|
10
|
+
One::Pivot.new
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
should "expect a block to construct" do
|
15
|
+
assert_raise LocalJumpError do
|
16
|
+
One::Pivot.new :test
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
should "store the identifier and pivot proc correctly" do
|
21
|
+
block = lambda {|item| item.class.name }
|
22
|
+
instance = One::Pivot.new(:test, &block)
|
23
|
+
assert_equal :test, instance.identifier
|
24
|
+
assert_equal block, instance.pivot_proc
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,268 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'one', 'pivot'))
|
3
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'one', 'pivoter'))
|
4
|
+
|
5
|
+
class PivotTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
context "A One::Pivot instance" do
|
8
|
+
setup do
|
9
|
+
@pivoter = One::Pivoter.new
|
10
|
+
end
|
11
|
+
|
12
|
+
should "pivot a simple array properly" do
|
13
|
+
list = [1,2,3,4,5,6,7,8,9]
|
14
|
+
result = @pivoter.pivot(list) {|item| item <= 5}
|
15
|
+
assert_equal [6,7,8,9], result[false]
|
16
|
+
assert_equal [1,2,3,4,5], result[true]
|
17
|
+
end
|
18
|
+
|
19
|
+
should "multi pivot a simple array properly" do
|
20
|
+
list = [1,2,3,4,5,6,7,8,9]
|
21
|
+
pivots = []
|
22
|
+
|
23
|
+
pivots << lambda do |i|
|
24
|
+
key = "less than or equal to 5" if i <= 5
|
25
|
+
key ||= "greater than 5"
|
26
|
+
end
|
27
|
+
|
28
|
+
pivots << lambda do |i|
|
29
|
+
key = "greater than or equal to 3" if i >= 3
|
30
|
+
key ||= "less than 3"
|
31
|
+
end
|
32
|
+
|
33
|
+
pivots << {:delimiter => " & "}
|
34
|
+
|
35
|
+
result = @pivoter.multi_pivot(list, *pivots)
|
36
|
+
|
37
|
+
# the result will be a Hash with the following structure:
|
38
|
+
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],
|
41
|
+
"greater than 5 & greater than or equal to 3" => [6, 7, 8, 9]
|
42
|
+
}
|
43
|
+
|
44
|
+
assert_equal hash, result
|
45
|
+
end
|
46
|
+
|
47
|
+
should "support both procs and One::Pivot objects with multi_pivot" do
|
48
|
+
list = [1,2,3,4,5,6,7,8,9]
|
49
|
+
pivots = []
|
50
|
+
|
51
|
+
pivots << One::Pivot.new("comparison to 5") do |i|
|
52
|
+
key = "less than or equal to 5" if i <= 5
|
53
|
+
key ||= "greater than 5"
|
54
|
+
end
|
55
|
+
|
56
|
+
pivots << One::Pivot.new("comparison to 3") do |i|
|
57
|
+
key = "greater than or equal to 3" if i >= 3
|
58
|
+
key ||= "less than 3"
|
59
|
+
end
|
60
|
+
|
61
|
+
pivots << {:delimiter => " & "}
|
62
|
+
|
63
|
+
result = @pivoter.multi_pivot(list, *pivots)
|
64
|
+
|
65
|
+
# the result will be a Hash with the following structure:
|
66
|
+
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],
|
69
|
+
"greater than 5 & greater than or equal to 3" => [6, 7, 8, 9]
|
70
|
+
}
|
71
|
+
|
72
|
+
assert_equal hash, result
|
73
|
+
end
|
74
|
+
|
75
|
+
should "support pivot observers (for example: to perform caching)" do
|
76
|
+
# Pivot operations can be expensive.
|
77
|
+
#
|
78
|
+
# Demonstrate how you can use the observer pattern to cache
|
79
|
+
# 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
|
82
|
+
# that spans its participation in several pivot calls
|
83
|
+
|
84
|
+
# extend Fixnum so we can cache pivot results
|
85
|
+
Fixnum.class_eval do
|
86
|
+
def pivot_cache
|
87
|
+
@pivot_cache ||= {}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
assert 1.respond_to?(:pivot_cache)
|
92
|
+
|
93
|
+
# create a pivot observer that caches pivot results per item
|
94
|
+
class PivotObserver
|
95
|
+
def initialize(pivoter)
|
96
|
+
pivoter.add_observer(self)
|
97
|
+
end
|
98
|
+
|
99
|
+
# this method gets invoked by the pivot object whenever a pivot block is executed
|
100
|
+
def update(identifier, item, value)
|
101
|
+
item.pivot_cache[identifier] = value
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
observer = PivotObserver.new(@pivoter)
|
106
|
+
|
107
|
+
list = [1,2,3,4,5,6,7,8,9]
|
108
|
+
result = @pivoter.pivot(list, :identifier => :less_than_5) do |item|
|
109
|
+
if item.pivot_cache.has_key?(:less_than_5)
|
110
|
+
raise Exception.new("Attempting to use a cached value that shouldn't exist")
|
111
|
+
else
|
112
|
+
item <= 5
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
list.each do |item|
|
117
|
+
assert item.pivot_cache.has_key?(:less_than_5)
|
118
|
+
end
|
119
|
+
|
120
|
+
# pivot again but this time use the pivot cache stored on each item
|
121
|
+
cached_result = @pivoter.pivot(list, :identifier => :less_than_5) do |item|
|
122
|
+
if item.pivot_cache.has_key?(:less_than_5)
|
123
|
+
item.pivot_cache[:less_than_5]
|
124
|
+
else
|
125
|
+
raise Exception.new("Attempting to calculate a value that should be cached")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
assert_equal result, cached_result
|
130
|
+
end
|
131
|
+
|
132
|
+
should "support pivot observers when using multi_pivot (for example: to perform caching)" do
|
133
|
+
# Pivot operations can be expensive.
|
134
|
+
#
|
135
|
+
# Demonstrate how you can use the observer pattern to cache
|
136
|
+
# 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
|
139
|
+
# that spans its participation in several pivot calls
|
140
|
+
|
141
|
+
# extend Fixnum so we can cache pivot results
|
142
|
+
Fixnum.class_eval do
|
143
|
+
def pivot_cache
|
144
|
+
@pivot_cache ||= {}
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
assert 1.respond_to?(:pivot_cache)
|
149
|
+
|
150
|
+
# create a pivot observer that caches pivot results per item
|
151
|
+
class PivotObserver
|
152
|
+
def initialize(pivoter)
|
153
|
+
pivoter.add_observer(self)
|
154
|
+
end
|
155
|
+
|
156
|
+
# this method gets invoked by the pivot object whenever a pivot block is executed
|
157
|
+
def update(identifier, item, value)
|
158
|
+
item.pivot_cache[identifier] = value
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
observer = PivotObserver.new(@pivoter)
|
163
|
+
|
164
|
+
list = [1,2,3,4,5,6,7,8,9]
|
165
|
+
|
166
|
+
pivots = []
|
167
|
+
|
168
|
+
pivots << One::Pivot.new("comparison to 5") do |i|
|
169
|
+
key = "less than or equal to 5" if i <= 5
|
170
|
+
key ||= "greater than 5"
|
171
|
+
end
|
172
|
+
|
173
|
+
pivots << One::Pivot.new("comparison to 3") do |i|
|
174
|
+
key = "greater than or equal to 3" if i >= 3
|
175
|
+
key ||= "less than 3"
|
176
|
+
end
|
177
|
+
|
178
|
+
result = @pivoter.multi_pivot(list, *pivots)
|
179
|
+
|
180
|
+
list.each do |item|
|
181
|
+
assert item.pivot_cache.has_key?("comparison to 5")
|
182
|
+
assert item.pivot_cache.has_key?("comparison to 3")
|
183
|
+
end
|
184
|
+
|
185
|
+
# pivot again but this time use the pivot cache stored on each item
|
186
|
+
pivots = []
|
187
|
+
|
188
|
+
pivots << One::Pivot.new("comparison to 5") do |i|
|
189
|
+
i.pivot_cache["comparison to 5"]
|
190
|
+
end
|
191
|
+
|
192
|
+
pivots << One::Pivot.new("comparison to 3") do |i|
|
193
|
+
i.pivot_cache["comparison to 3"]
|
194
|
+
end
|
195
|
+
|
196
|
+
cached_result = @pivoter.multi_pivot(list, *pivots)
|
197
|
+
assert_equal result, cached_result
|
198
|
+
end
|
199
|
+
|
200
|
+
context "working on complex data structures" do
|
201
|
+
|
202
|
+
setup do
|
203
|
+
users = []
|
204
|
+
users << {:name => "Frank", :gender => "M", :roles => []}
|
205
|
+
users << {:name => "Joe", :gender => "M", :roles => []}
|
206
|
+
users << {:name => "John", :gender => "M", :roles => []}
|
207
|
+
users << {:name => "Sally", :gender => "F", :roles => []}
|
208
|
+
|
209
|
+
# assign some roles to the users
|
210
|
+
|
211
|
+
# Frank gets Read
|
212
|
+
users[0][:roles] << "Read"
|
213
|
+
|
214
|
+
# Joe gets Write
|
215
|
+
users[1][:roles] << "Write"
|
216
|
+
|
217
|
+
# John gets Read & Write
|
218
|
+
users[2][:roles] << "Read"
|
219
|
+
users[2][:roles] << "Write"
|
220
|
+
|
221
|
+
# Sally gets no roles
|
222
|
+
|
223
|
+
@users = users
|
224
|
+
end
|
225
|
+
|
226
|
+
should "pivot Array values individually using a nil key for empty Arrays" do
|
227
|
+
result = @pivoter.pivot(@users) {|user| user[:roles]}
|
228
|
+
|
229
|
+
# this is what the resulting hash should look like:
|
230
|
+
hash = {
|
231
|
+
nil => [
|
232
|
+
{:name=>"Sally", :gender => "F", :roles=>[]}],
|
233
|
+
"Read" => [
|
234
|
+
{:name=>"Frank", :gender => "M", :roles=>["Read"]},
|
235
|
+
{:name=>"John", :gender => "M", :roles=>["Read", "Write"]}],
|
236
|
+
"Write" => [
|
237
|
+
{:name=>"Joe", :gender => "M", :roles=>["Write"]},
|
238
|
+
{:name=>"John", :gender => "M", :roles=>["Read", "Write"]}]
|
239
|
+
}
|
240
|
+
|
241
|
+
assert_equal hash, result
|
242
|
+
end
|
243
|
+
|
244
|
+
should "stack pivots with multi_pivot" do
|
245
|
+
pivots = []
|
246
|
+
pivots << lambda {|user| user[:gender]}
|
247
|
+
pivots << lambda {|user| user[:roles]}
|
248
|
+
|
249
|
+
result = @pivoter.multi_pivot(@users, *pivots)
|
250
|
+
|
251
|
+
# this is what the resulting hash should look like
|
252
|
+
hash = {
|
253
|
+
"M[PIVOT]Write" => [
|
254
|
+
{:name=>"Joe", :gender=>"M", :roles=>["Write"]},
|
255
|
+
{:name=>"John", :gender=>"M", :roles=>["Read", "Write"]}],
|
256
|
+
"M[PIVOT]Read" => [
|
257
|
+
{:name=>"Frank", :gender=>"M", :roles=>["Read"]},
|
258
|
+
{:name=>"John", :gender=>"M", :roles=>["Read", "Write"]}],
|
259
|
+
"F[PIVOT]nil" => [
|
260
|
+
{:name=>"Sally", :gender=>"F", :roles=>[]}]
|
261
|
+
}
|
262
|
+
|
263
|
+
assert_equal hash, result
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: one-pivot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.9.2
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nathan Hopkins
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-02-23 00:00:00 -07:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: shoulda
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - "="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 2.11.3
|
25
|
+
type: :development
|
26
|
+
version_requirements: *id001
|
27
|
+
description: " Pivot provides basic pivoting functionality for sorting an Array of objects.\n"
|
28
|
+
email:
|
29
|
+
- natehop@gmail.com
|
30
|
+
- natehop@1on1.com
|
31
|
+
executables: []
|
32
|
+
|
33
|
+
extensions: []
|
34
|
+
|
35
|
+
extra_rdoc_files: []
|
36
|
+
|
37
|
+
files:
|
38
|
+
- lib/one/pivot.rb
|
39
|
+
- lib/one/pivoter.rb
|
40
|
+
- lib/one-pivot.rb
|
41
|
+
- Gemfile
|
42
|
+
- Gemfile.lock
|
43
|
+
- Rakefile
|
44
|
+
- README.md
|
45
|
+
- test/pivot_test.rb
|
46
|
+
- test/pivoter_test.rb
|
47
|
+
- test/test_helper.rb
|
48
|
+
has_rdoc: true
|
49
|
+
homepage: https://github.com/one-on-one/pivot
|
50
|
+
licenses:
|
51
|
+
- MIT
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
requirements: []
|
70
|
+
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.5.2
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: Pivot provides basic pivoting functionality for sorting an Array of objects.
|
76
|
+
test_files: []
|
77
|
+
|