one-pivot 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source :rubygems
2
+
3
+ gem 'shoulda', '2.11.3'
4
+ gem 'yard', '0.6.4'
5
+ gem 'bluecloth', '2.0.10'
6
+
7
+
data/Gemfile.lock ADDED
@@ -0,0 +1,14 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ bluecloth (2.0.10)
5
+ shoulda (2.11.3)
6
+ yard (0.6.4)
7
+
8
+ PLATFORMS
9
+ ruby
10
+
11
+ DEPENDENCIES
12
+ bluecloth (= 2.0.10)
13
+ shoulda (= 2.11.3)
14
+ yard (= 0.6.4)
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
@@ -0,0 +1,10 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.test_files = FileList['test/*_test.rb']
7
+ end
8
+
9
+ task 'test:units' => ['test'] do
10
+ end
data/lib/one-pivot.rb ADDED
@@ -0,0 +1,2 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'one', 'pivot'))
2
+ require File.expand_path(File.join(File.dirname(__FILE__), 'one', 'pivoter'))
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
@@ -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
+
@@ -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
@@ -0,0 +1,2 @@
1
+ require 'rubygems'
2
+ require 'shoulda'
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
+