pivot_table 0.0.3 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 [](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
|