pivot_table 0.0.3 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.rvmrc +16 -15
- data/.travis.yml +2 -0
- data/Gemfile +4 -1
- data/README.md +67 -38
- data/Rakefile +5 -0
- data/lib/pivot_table.rb +5 -2
- data/lib/pivot_table/column.rb +17 -0
- data/lib/pivot_table/grid.rb +66 -0
- data/lib/pivot_table/row.rb +17 -0
- data/pivot_table.gemspec +2 -1
- data/spec/pivot_table/column_spec.rb +23 -0
- data/spec/pivot_table/grid_spec.rb +112 -0
- data/spec/pivot_table/row_spec.rb +23 -0
- metadata +13 -8
- data/lib/pivot_table/table.rb +0 -73
- data/spec/pivot_table/table_spec.rb +0 -120
data/.rvmrc
CHANGED
@@ -1,21 +1,22 @@
|
|
1
|
-
|
2
|
-
gemset_name="pivot"
|
1
|
+
#!/usr/bin/env bash
|
3
2
|
|
4
|
-
|
5
|
-
warn="\033[1;31m"
|
6
|
-
reset="\033[0m"
|
3
|
+
environment_id="ruby-1.9.2-p290@pivot_table"
|
7
4
|
|
8
|
-
if
|
9
|
-
|
5
|
+
if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
|
6
|
+
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
|
7
|
+
then
|
8
|
+
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
|
10
9
|
|
11
|
-
if
|
12
|
-
|
13
|
-
|
14
|
-
else
|
15
|
-
echo -e "${ok}The ${gemset_name} gemset does not exist, using global gemset${reset}"
|
10
|
+
if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
|
11
|
+
then
|
12
|
+
. "${rvm_path:-$HOME/.rvm}/hooks/after_use"
|
16
13
|
fi
|
17
|
-
|
14
|
+
echo "RVM environment: ${environment_id}"
|
18
15
|
else
|
19
|
-
|
20
|
-
|
16
|
+
# If the environment file has not yet been created, use the RVM CLI to select.
|
17
|
+
if ! rvm --create "$environment_id"
|
18
|
+
then
|
19
|
+
echo "Failed to create RVM environment '${environment_id}'."
|
20
|
+
exit 1
|
21
|
+
fi
|
21
22
|
fi
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
@@ -3,12 +3,15 @@ source "http://rubygems.org"
|
|
3
3
|
# Specify your gem's dependencies in pivot.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
|
+
gem 'rake'
|
7
|
+
|
6
8
|
group :development, :test do
|
7
9
|
gem 'autotest'
|
8
|
-
gem 'autotest-growl'
|
10
|
+
#gem 'autotest-growl'
|
9
11
|
gem 'autotest-notification'
|
10
12
|
gem 'bundler'
|
11
13
|
gem 'launchy'
|
12
14
|
gem 'rcov'
|
13
15
|
gem 'rspec'
|
16
|
+
gem 'shoulda-matchers'
|
14
17
|
end
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Pivot Table
|
1
|
+
Pivot Table [![Build Status](https://secure.travis-ci.org/edjames/pivot_table.png)](http://travis-ci.org/edjames/pivot_table)
|
2
2
|
===========
|
3
3
|
|
4
4
|
A handy tool for transforming a dataset into a spreadsheet-style pivot table.
|
@@ -7,7 +7,7 @@ Why make this?
|
|
7
7
|
--------------
|
8
8
|
|
9
9
|
One of the most powerful and underrated features of spreadhseet packages is their ability to create pivot tables. I'm often asked
|
10
|
-
to replicate this functionality in a web application
|
10
|
+
to replicate this functionality in a web application, so I decided to share. This is a simple gem for a specific job, I hope it helps.
|
11
11
|
|
12
12
|
Installation
|
13
13
|
------------
|
@@ -26,47 +26,70 @@ At the very least, you will need to provide four things to create a pivot table.
|
|
26
26
|
* a dataset (this doesn't necessarily have to be an ActiveRecord dataset, but it should at least behave like ActiveRecord e.g. OpenStruct)
|
27
27
|
* the method to be used as column names
|
28
28
|
* the method to be used as row names
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
p.pivot_on = :sales
|
29
|
+
|
30
|
+
Let's say you have a collection of Order objects that looks like this:
|
31
|
+
|
32
|
+
obj_1 = Order.new(:city => 'London', :quarter => 'Q1')
|
33
|
+
obj_2 = Order.new(:city => 'London', :quarter => 'Q2')
|
34
|
+
obj_3 = Order.new(:city => 'London', :quarter => 'Q3')
|
35
|
+
obj_4 = Order.new(:city => 'London', :quarter => 'Q4')
|
36
|
+
obj_5 = Order.new(:city => 'New York', :quarter => 'Q1')
|
37
|
+
obj_6 = Order.new(:city => 'New York', :quarter => 'Q2')
|
38
|
+
obj_7 = Order.new(:city => 'New York', :quarter => 'Q3')
|
39
|
+
obj_8 = Order.new(:city => 'New York', :quarter => 'Q4')
|
40
|
+
|
41
|
+
data = [ obj_1, obj_2, obj_3, obj_4, obj_5, obj_6, obj_7, obj_8 ]
|
42
|
+
|
43
|
+
Instantiate a new PivotTable::Grid object like this...
|
44
|
+
|
45
|
+
grid = PivotTable::Grid.new do |g|
|
46
|
+
g.sourcedata = data
|
47
|
+
g.column_name = :quarter
|
48
|
+
g.row_name = :city
|
50
49
|
end
|
51
|
-
p.generate
|
52
50
|
|
53
|
-
...which will give you a hash that looks like this...
|
54
51
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
52
|
+
All you have to do now is build the grid...
|
53
|
+
|
54
|
+
g.build
|
55
|
+
|
56
|
+
This will give you a logical grid (represented by an two-dimensional array) which can be likened to this table:
|
57
|
+
|
58
|
+
--------------------------------------------
|
59
|
+
| | Q1 | Q2 | Q3 | Q4 |
|
60
|
+
|----------|--------------------------------
|
61
|
+
| London | obj_1 | obj_2 | obj_3 | obj_4 |
|
62
|
+
| New York | obj_5 | obj_6 | obj_7 | obj_8 |
|
63
|
+
--------------------------------------------
|
64
|
+
|
65
|
+
Then you have the following aspects of the pivot table grid available to you...
|
63
66
|
|
64
|
-
|
67
|
+
g.row_headers => ['London', 'New York']
|
68
|
+
g.rows.length => 2
|
65
69
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
+
g.rows[0].header => 'London'
|
71
|
+
g.rows[0].data => [obj_1, obj_2, obj_3, obj_4]
|
72
|
+
|
73
|
+
g.rows[1].header => 'New York'
|
74
|
+
g.rows[1].data => [obj_5, obj_6, obj_7, obj_8]
|
75
|
+
|
76
|
+
g.column_headers => ['Q1', 'Q2', 'Q3', 'Q4']
|
77
|
+
g.columns.length => 4
|
78
|
+
|
79
|
+
g.columns[0].header => 'Q1'
|
80
|
+
g.columns[0].data => [obj_1, obj_5]
|
81
|
+
|
82
|
+
g.columns[1].header => 'Q2'
|
83
|
+
g.columns[1].data => [obj_2, obj_6]
|
84
|
+
|
85
|
+
g.columns[2].header => 'Q3'
|
86
|
+
g.columns[2].data => [obj_3, obj_7]
|
87
|
+
|
88
|
+
g.columns[3].header => 'Q4'
|
89
|
+
g.columns[3].data => [obj_4, obj_8]
|
90
|
+
|
91
|
+
The API should give you a lot of flexibility with regards to rendering this information in your views.
|
92
|
+
E.g. The rows and columns collections make it very easy to produce horizontal, vertical and overall total values.
|
70
93
|
|
71
94
|
Ah, that's better.
|
72
95
|
|
@@ -76,6 +99,12 @@ Still to come
|
|
76
99
|
PivotTable is still in the very early stages of development. As my personal needs for evolve, I'll update the gem with new functionality accordingly.
|
77
100
|
Feel free to fork and/or suggest new features.
|
78
101
|
|
102
|
+
Ruby 1.9 only...for now
|
103
|
+
----------------
|
104
|
+
|
105
|
+
Right now PivotTable only supports Ruby 1.9. If you need support for 1.8 please feel free to fork and merge. I will not however be adding
|
106
|
+
support for 1.8.
|
107
|
+
|
79
108
|
Contributing to PivotTable
|
80
109
|
---------------------
|
81
110
|
|
data/Rakefile
CHANGED
data/lib/pivot_table.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
module PivotTable
|
2
|
+
class Column
|
3
|
+
|
4
|
+
ACCESSORS = [:header, :data, :total]
|
5
|
+
|
6
|
+
ACCESSORS.each do |a|
|
7
|
+
self.send(:attr_accessor, a)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(options = {})
|
11
|
+
ACCESSORS.each do |a|
|
12
|
+
self.send("#{a}=", options[a]) if options.has_key?(a)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module PivotTable
|
2
|
+
class Grid
|
3
|
+
|
4
|
+
attr_accessor :source_data, :row_name, :column_name
|
5
|
+
attr_reader :columns, :rows, :data_grid
|
6
|
+
|
7
|
+
def initialize(&block)
|
8
|
+
yield(self) if block_given?
|
9
|
+
end
|
10
|
+
|
11
|
+
def build
|
12
|
+
populate_grid
|
13
|
+
build_rows
|
14
|
+
build_columns
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def build_rows
|
19
|
+
@rows = []
|
20
|
+
@data_grid.each_with_index do |data, index|
|
21
|
+
@rows << Row.new(:header => row_headers[index], :data => data)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def build_columns
|
26
|
+
@columns = []
|
27
|
+
@data_grid.transpose.each_with_index do |data, index|
|
28
|
+
@columns << Column.new(:header => column_headers[index], :data => data)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def column_headers
|
33
|
+
headers @column_name
|
34
|
+
end
|
35
|
+
|
36
|
+
def row_headers
|
37
|
+
headers @row_name
|
38
|
+
end
|
39
|
+
|
40
|
+
def prepare_grid
|
41
|
+
@data_grid = []
|
42
|
+
row_headers.count.times do
|
43
|
+
@data_grid << column_headers.count.times.inject([]) { |col| col << nil }
|
44
|
+
end
|
45
|
+
@data_grid
|
46
|
+
end
|
47
|
+
|
48
|
+
def populate_grid
|
49
|
+
prepare_grid
|
50
|
+
row_headers.each_with_index do |row, row_index|
|
51
|
+
current_row = []
|
52
|
+
column_headers.each_with_index do |col, col_index|
|
53
|
+
current_row[col_index] = @source_data.find { |item| item.send(row_name) == row && item.send(column_name) == col }
|
54
|
+
end
|
55
|
+
@data_grid[row_index] = current_row
|
56
|
+
end
|
57
|
+
@data_grid
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
def headers method
|
62
|
+
@source_data.collect { |c| c.send method }.uniq.sort
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module PivotTable
|
2
|
+
class Row
|
3
|
+
|
4
|
+
ACCESSORS = [:header, :data, :total]
|
5
|
+
|
6
|
+
ACCESSORS.each do |a|
|
7
|
+
self.send(:attr_accessor, a)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(options = {})
|
11
|
+
ACCESSORS.each do |a|
|
12
|
+
self.send("#{a}=", options[a]) if options.has_key?(a)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
data/pivot_table.gemspec
CHANGED
@@ -9,9 +9,10 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.email = Base64.decode64("ZWQuamFtZXMuZW1haWxAZ21haWwuY29t\n")
|
10
10
|
s.homepage = "https://github.com/edjames/pivot_table"
|
11
11
|
s.summary = "pivot_table-#{s.version}"
|
12
|
-
s.description = "Transform an ActiveRecord-ish data set into pivot table"
|
12
|
+
s.description = "Transform an ActiveRecord-ish data set into a pivot table of objects"
|
13
13
|
|
14
14
|
s.platform = Gem::Platform::RUBY
|
15
|
+
s.required_ruby_version = '>= 1.9'
|
15
16
|
|
16
17
|
s.rubyforge_project = "pivot_table"
|
17
18
|
s.rubygems_version = ">= 1.6.1"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module PivotTable
|
4
|
+
describe Column do
|
5
|
+
|
6
|
+
let(:klass) { Column }
|
7
|
+
|
8
|
+
before { @instance = klass.new }
|
9
|
+
|
10
|
+
it { should respond_to :header }
|
11
|
+
it { should respond_to :data }
|
12
|
+
it { should respond_to :total }
|
13
|
+
|
14
|
+
context 'initialize with hash' do
|
15
|
+
subject { klass.new(header: 'header', data: 'data', total: 'total')}
|
16
|
+
|
17
|
+
its(:header) { should == 'header' }
|
18
|
+
its(:data) { should == 'data' }
|
19
|
+
its(:total) { should == 'total' }
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module PivotTable
|
4
|
+
describe Grid do
|
5
|
+
def build_object(id, row, column)
|
6
|
+
OpenStruct.new id: id, row_name: row, column_name: column
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:d1) { build_object(1, 'r1', 'c1') }
|
10
|
+
let(:d2) { build_object(2, 'r1', 'c2') }
|
11
|
+
let(:d3) { build_object(3, 'r1', 'c3') }
|
12
|
+
let(:d4) { build_object(4, 'r2', 'c1') }
|
13
|
+
let(:d5) { build_object(5, 'r2', 'c2') }
|
14
|
+
let(:d6) { build_object(6, 'r2', 'c3') }
|
15
|
+
|
16
|
+
let(:data) { [d1, d2, d3, d4, d5, d6] }
|
17
|
+
|
18
|
+
let(:column_headers) { %w(c1 c2 c3) }
|
19
|
+
let(:row_headers) { %w(r1 r2) }
|
20
|
+
let(:row_0) { [d1, d2, d3] }
|
21
|
+
let(:row_1) { [d4, d5, d6] }
|
22
|
+
let(:column_0) { [d1, d4] }
|
23
|
+
let(:column_1) { [d2, d5] }
|
24
|
+
let(:column_2) { [d3, d6] }
|
25
|
+
|
26
|
+
let(:instance) do
|
27
|
+
Grid.new do |g|
|
28
|
+
g.source_data = data
|
29
|
+
g.row_name = :row_name
|
30
|
+
g.column_name = :column_name
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'accessors' do
|
35
|
+
subject { Grid.new }
|
36
|
+
it { should respond_to :source_data }
|
37
|
+
it { should respond_to :row_name }
|
38
|
+
it { should respond_to :column_name }
|
39
|
+
it { should respond_to :columns }
|
40
|
+
it { should respond_to :rows }
|
41
|
+
end
|
42
|
+
|
43
|
+
describe 'build' do
|
44
|
+
subject { instance.build }
|
45
|
+
its(:class) { should == Grid }
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'columns' do
|
49
|
+
let(:build_result) { instance.build }
|
50
|
+
specify { build_result.columns.length.should == 3 }
|
51
|
+
|
52
|
+
context 'column headers' do
|
53
|
+
subject { build_result.column_headers }
|
54
|
+
it { should == column_headers }
|
55
|
+
end
|
56
|
+
|
57
|
+
context '1st column' do
|
58
|
+
subject { build_result.columns[0] }
|
59
|
+
its(:header) { should == column_headers[0] }
|
60
|
+
its(:data) { should = column_0 }
|
61
|
+
end
|
62
|
+
|
63
|
+
context '2nd column' do
|
64
|
+
subject { build_result.columns[1] }
|
65
|
+
its(:header) { should == column_headers[1] }
|
66
|
+
its(:data) { should = column_1 }
|
67
|
+
end
|
68
|
+
|
69
|
+
context '3rd column' do
|
70
|
+
subject { build_result.columns[2] }
|
71
|
+
its(:header) { should == column_headers[2] }
|
72
|
+
its(:data) { should = column_2 }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'rows' do
|
77
|
+
let(:build_result) { instance.build }
|
78
|
+
specify { build_result.rows.length.should == 2 }
|
79
|
+
|
80
|
+
context 'row headers' do
|
81
|
+
subject { build_result.row_headers }
|
82
|
+
it { should == row_headers }
|
83
|
+
end
|
84
|
+
|
85
|
+
context '1st row' do
|
86
|
+
subject { build_result.rows[0] }
|
87
|
+
its(:header) { should == row_headers[0] }
|
88
|
+
its(:data) { should = row_0 }
|
89
|
+
end
|
90
|
+
|
91
|
+
context '2nd row' do
|
92
|
+
subject { build_result.rows[1] }
|
93
|
+
its(:header) { should == row_headers[1] }
|
94
|
+
its(:data) { should = row_1 }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe 'data grid' do
|
99
|
+
let(:build_result) { instance.build }
|
100
|
+
|
101
|
+
context 'preparing the grid' do
|
102
|
+
subject { build_result.prepare_grid }
|
103
|
+
it { should == [[nil, nil, nil], [nil, nil, nil]] }
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'populating the grid' do
|
107
|
+
subject { build_result.data_grid }
|
108
|
+
it { should == [[d1, d2, d3], [d4, d5, d6]] }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module PivotTable
|
4
|
+
describe Row do
|
5
|
+
|
6
|
+
let(:klass) { Row }
|
7
|
+
|
8
|
+
before { @instance = klass.new }
|
9
|
+
|
10
|
+
it { should respond_to :header }
|
11
|
+
it { should respond_to :data }
|
12
|
+
it { should respond_to :total }
|
13
|
+
|
14
|
+
context 'initialize with hash' do
|
15
|
+
subject { klass.new(header: 'header', data: 'data', total: 'total')}
|
16
|
+
|
17
|
+
its(:header) { should == 'header' }
|
18
|
+
its(:data) { should == 'data' }
|
19
|
+
its(:total) { should == 'total' }
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pivot_table
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,9 +9,9 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-
|
12
|
+
date: 2011-09-28 00:00:00.000000000Z
|
13
13
|
dependencies: []
|
14
|
-
description: Transform an ActiveRecord-ish data set into pivot table
|
14
|
+
description: Transform an ActiveRecord-ish data set into a pivot table of objects
|
15
15
|
email: ed.james.email@gmail.com
|
16
16
|
executables: []
|
17
17
|
extensions: []
|
@@ -20,15 +20,20 @@ files:
|
|
20
20
|
- .gitignore
|
21
21
|
- .rspec
|
22
22
|
- .rvmrc
|
23
|
+
- .travis.yml
|
23
24
|
- CHANGELOG.md
|
24
25
|
- Gemfile
|
25
26
|
- LICENSE
|
26
27
|
- README.md
|
27
28
|
- Rakefile
|
28
29
|
- lib/pivot_table.rb
|
29
|
-
- lib/pivot_table/
|
30
|
+
- lib/pivot_table/column.rb
|
31
|
+
- lib/pivot_table/grid.rb
|
32
|
+
- lib/pivot_table/row.rb
|
30
33
|
- pivot_table.gemspec
|
31
|
-
- spec/pivot_table/
|
34
|
+
- spec/pivot_table/column_spec.rb
|
35
|
+
- spec/pivot_table/grid_spec.rb
|
36
|
+
- spec/pivot_table/row_spec.rb
|
32
37
|
- spec/spec_helper.rb
|
33
38
|
homepage: https://github.com/edjames/pivot_table
|
34
39
|
licenses: []
|
@@ -41,7 +46,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
41
46
|
requirements:
|
42
47
|
- - ! '>='
|
43
48
|
- !ruby/object:Gem::Version
|
44
|
-
version: '
|
49
|
+
version: '1.9'
|
45
50
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
51
|
none: false
|
47
52
|
requirements:
|
@@ -50,8 +55,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
55
|
version: '0'
|
51
56
|
requirements: []
|
52
57
|
rubyforge_project: pivot_table
|
53
|
-
rubygems_version: 1.8.
|
58
|
+
rubygems_version: 1.8.6
|
54
59
|
signing_key:
|
55
60
|
specification_version: 3
|
56
|
-
summary: pivot_table-0.
|
61
|
+
summary: pivot_table-0.1.1
|
57
62
|
test_files: []
|
data/lib/pivot_table/table.rb
DELETED
@@ -1,73 +0,0 @@
|
|
1
|
-
module PivotTable
|
2
|
-
class Table
|
3
|
-
|
4
|
-
attr_accessor :data, :column, :row, :pivot_on
|
5
|
-
|
6
|
-
def initialize(&block)
|
7
|
-
yield(self) if block_given?
|
8
|
-
end
|
9
|
-
|
10
|
-
def column_headers
|
11
|
-
@data.collect { |c| c.send @column }.uniq.sort
|
12
|
-
end
|
13
|
-
|
14
|
-
def row_headers
|
15
|
-
@data.collect { |r| r.send @row }.uniq.sort
|
16
|
-
end
|
17
|
-
|
18
|
-
def initialize_grid(rows, columns, default_value = 0)
|
19
|
-
@grid = []
|
20
|
-
rows.times do
|
21
|
-
@grid << columns.times.inject([]) { |col| col << default_value }
|
22
|
-
end
|
23
|
-
@grid
|
24
|
-
end
|
25
|
-
|
26
|
-
def matching_data(row_header, column_header)
|
27
|
-
data.select { |item| item.send(@row) == row_header && item.send(@column) == column_header }
|
28
|
-
end
|
29
|
-
|
30
|
-
def populate_grid
|
31
|
-
row_headers.each_with_index do |row_header, row_index|
|
32
|
-
column_headers.each_with_index do |column_header, column_index|
|
33
|
-
collected = matching_data row_header, column_header
|
34
|
-
total = collected.inject(0) { |sum, item| sum += item.send(@pivot_on) }
|
35
|
-
@grid[row_index][column_index] += total
|
36
|
-
end
|
37
|
-
end
|
38
|
-
@grid
|
39
|
-
end
|
40
|
-
|
41
|
-
def row_sum row
|
42
|
-
row.inject(0) { |sum, value| sum += value }
|
43
|
-
end
|
44
|
-
|
45
|
-
def calculate_row_totals
|
46
|
-
@grid.each { |row| row << row_sum(row) }
|
47
|
-
end
|
48
|
-
|
49
|
-
def add_row_headers
|
50
|
-
@grid.each_with_index do |row, index|
|
51
|
-
row.unshift row_headers[index]
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def calculate_column_totals data
|
56
|
-
numeric_data = data.dup
|
57
|
-
numeric_data.transpose.map { |row| row_sum row }
|
58
|
-
end
|
59
|
-
|
60
|
-
def generate
|
61
|
-
initialize_grid row_headers.size, column_headers.size, 0
|
62
|
-
populate_grid
|
63
|
-
calculate_row_totals
|
64
|
-
totals = calculate_column_totals @grid
|
65
|
-
|
66
|
-
{
|
67
|
-
:headers => ['', column_headers, 'Total'].flatten,
|
68
|
-
:rows => add_row_headers,
|
69
|
-
:totals => ['Total', totals].flatten
|
70
|
-
}
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
@@ -1,120 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
module PivotTable
|
4
|
-
describe Table do
|
5
|
-
context 'accessors' do
|
6
|
-
subject { Table.new }
|
7
|
-
it { should respond_to :data }
|
8
|
-
it { should respond_to :column }
|
9
|
-
it { should respond_to :row }
|
10
|
-
it { should respond_to :pivot_on }
|
11
|
-
end
|
12
|
-
|
13
|
-
context 'instantiate using explicit block form' do
|
14
|
-
subject do
|
15
|
-
Table.new do |i|
|
16
|
-
i.data = :data
|
17
|
-
i.column = :column
|
18
|
-
i.row = :row
|
19
|
-
i.pivot_on = :pivot_on
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
specify { subject.data.should == :data }
|
24
|
-
specify { subject.column.should == :column }
|
25
|
-
specify { subject.row.should == :row }
|
26
|
-
specify { subject.pivot_on.should == :pivot_on }
|
27
|
-
end
|
28
|
-
|
29
|
-
before do
|
30
|
-
@data = []
|
31
|
-
@data << OpenStruct.new(:city => 'London', :quarter => 'Q1', :sales => 100)
|
32
|
-
@data << OpenStruct.new(:city => 'London', :quarter => 'Q2', :sales => 200)
|
33
|
-
@data << OpenStruct.new(:city => 'London', :quarter => 'Q3', :sales => 300)
|
34
|
-
@data << OpenStruct.new(:city => 'London', :quarter => 'Q4', :sales => 400)
|
35
|
-
@data << OpenStruct.new(:city => 'Cape Town', :quarter => 'Q1', :sales => 5)
|
36
|
-
@data << OpenStruct.new(:city => 'Cape Town', :quarter => 'Q1', :sales => 15)
|
37
|
-
@data << OpenStruct.new(:city => 'Cape Town', :quarter => 'Q1', :sales => 30)
|
38
|
-
@data << OpenStruct.new(:city => 'New York', :quarter => 'Q1', :sales => 10)
|
39
|
-
@data << OpenStruct.new(:city => 'New York', :quarter => 'Q2', :sales => 20)
|
40
|
-
@data << OpenStruct.new(:city => 'New York', :quarter => 'Q3', :sales => 30)
|
41
|
-
@data << OpenStruct.new(:city => 'New York', :quarter => 'Q4', :sales => 10)
|
42
|
-
@data << OpenStruct.new(:city => 'New York', :quarter => 'Q4', :sales => 30)
|
43
|
-
@instance = Table.new do |i|
|
44
|
-
i.data = @data
|
45
|
-
i.column = :quarter
|
46
|
-
i.row = :city
|
47
|
-
i.pivot_on = :sales
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
subject { @instance }
|
52
|
-
|
53
|
-
context 'column headers' do
|
54
|
-
subject { @instance.column_headers }
|
55
|
-
it { should == ['Q1', 'Q2', 'Q3', 'Q4'] }
|
56
|
-
end
|
57
|
-
|
58
|
-
context 'row headers' do
|
59
|
-
subject { @instance.row_headers }
|
60
|
-
it { should == ['Cape Town', 'London', 'New York'] }
|
61
|
-
end
|
62
|
-
|
63
|
-
context 'initializing the grid' do
|
64
|
-
before do
|
65
|
-
@expected_result = [
|
66
|
-
[0, 0, 0],
|
67
|
-
[0, 0, 0]
|
68
|
-
]
|
69
|
-
end
|
70
|
-
subject { @instance.initialize_grid 2, 3, 0 }
|
71
|
-
it { should == @expected_result }
|
72
|
-
end
|
73
|
-
|
74
|
-
context 'matching data' do
|
75
|
-
context 'single matching data item' do
|
76
|
-
subject { @instance.matching_data 'London', 'Q1' }
|
77
|
-
specify { subject.size.should == 1 }
|
78
|
-
specify { subject.first.should == @data.first }
|
79
|
-
end
|
80
|
-
|
81
|
-
context 'multiple matching data items' do
|
82
|
-
subject { @instance.matching_data 'Cape Town', 'Q1' }
|
83
|
-
specify { subject.size.should == 3 }
|
84
|
-
specify { subject[0].should == @data[4] }
|
85
|
-
specify { subject[1].should == @data[5] }
|
86
|
-
specify { subject[2].should == @data[6] }
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
context 'aggregating the data in the grid' do
|
91
|
-
before do
|
92
|
-
@instance.initialize_grid 3, 4, 0
|
93
|
-
@expected_result = [
|
94
|
-
[50, 0, 0, 0],
|
95
|
-
[100, 200, 300, 400],
|
96
|
-
[10, 20, 30, 40]
|
97
|
-
]
|
98
|
-
end
|
99
|
-
subject { @instance.populate_grid }
|
100
|
-
it { should == @expected_result }
|
101
|
-
end
|
102
|
-
|
103
|
-
context 'generate' do
|
104
|
-
before do
|
105
|
-
@expected_result = {
|
106
|
-
:headers => ['', 'Q1', 'Q2', 'Q3', 'Q4', 'Total'],
|
107
|
-
:rows => [
|
108
|
-
['Cape Town', 50, 0, 0, 0, 50],
|
109
|
-
['London', 100, 200, 300, 400, 1000],
|
110
|
-
['New York', 10, 20, 30, 40, 100]
|
111
|
-
],
|
112
|
-
:totals => ['Total', 160, 220, 330, 440, 1150]
|
113
|
-
}
|
114
|
-
end
|
115
|
-
subject { @instance.generate }
|
116
|
-
it { should == @expected_result }
|
117
|
-
end
|
118
|
-
|
119
|
-
end
|
120
|
-
end
|