pivot_table 0.0.3

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/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .idea
2
+ *.gem
3
+ .bundle
4
+ Gemfile.lock
5
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format progress
data/.rvmrc ADDED
@@ -0,0 +1,21 @@
1
+ ruby_version="1.9.2"
2
+ gemset_name="pivot"
3
+
4
+ ok="\033[10;32m"
5
+ warn="\033[1;31m"
6
+ reset="\033[0m"
7
+
8
+ if rvm list strings | grep -q "${ruby_version}" ; then
9
+ rvm use ${ruby_version}
10
+
11
+ if rvm gemset list | grep -q "${gemset_name}" ; then
12
+ echo -e "${ok}Using the ${gemset_name} gemset${reset}"
13
+ rvm gemset use ${gemset_name}
14
+ else
15
+ echo -e "${ok}The ${gemset_name} gemset does not exist, using global gemset${reset}"
16
+ fi
17
+
18
+ else
19
+ echo -e "${warn}You do not have the required Ruby installed: ${ruby_version}${reset}"
20
+
21
+ fi
data/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ Changelog
2
+ =========
3
+
4
+ 0.0.3
5
+ -----
6
+
7
+ Renamed gem to pivot_table
8
+
9
+ 0.0.2
10
+ -----
11
+
12
+ Major refactoring to allow unbalanced dataset to be pivoted.
13
+
14
+ 0.0.1
15
+ -----
16
+
17
+ Initial release.
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in pivot.gemspec
4
+ gemspec
5
+
6
+ group :development, :test do
7
+ gem 'autotest'
8
+ gem 'autotest-growl'
9
+ gem 'autotest-notification'
10
+ gem 'bundler'
11
+ gem 'launchy'
12
+ gem 'rcov'
13
+ gem 'rspec'
14
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Ed James
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,95 @@
1
+ Pivot Table
2
+ ===========
3
+
4
+ A handy tool for transforming a dataset into a spreadsheet-style pivot table.
5
+
6
+ Why make this?
7
+ --------------
8
+
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...
11
+
12
+ Installation
13
+ ------------
14
+
15
+ Couldn't be easier...
16
+
17
+ gem install pivot_table
18
+
19
+ There are no dependencies and pivot will work on any version of Ruby.
20
+
21
+ Usage
22
+ -----
23
+
24
+ At the very least, you will need to provide four things to create a pivot table...
25
+
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
+ * the method to be used as column names
28
+ * the method to be used as row names
29
+ * the method to be used as the pivot
30
+
31
+ Let's say you have a dataset that looks like this (I'll use OpenStruct, but this could easily be ActiveRecord, or even a custom object):
32
+
33
+ my_data = []
34
+ my_data << OpenStruct.new(:city => 'London', :quarter => 'Q1', :sales => 100)
35
+ my_data << OpenStruct.new(:city => 'London', :quarter => 'Q2', :sales => 200)
36
+ my_data << OpenStruct.new(:city => 'London', :quarter => 'Q3', :sales => 300)
37
+ my_data << OpenStruct.new(:city => 'London', :quarter => 'Q4', :sales => 400)
38
+ my_data << OpenStruct.new(:city => 'New York', :quarter => 'Q1', :sales => 10)
39
+ my_data << OpenStruct.new(:city => 'New York', :quarter => 'Q2', :sales => 20)
40
+ my_data << OpenStruct.new(:city => 'New York', :quarter => 'Q3', :sales => 30)
41
+ my_data << OpenStruct.new(:city => 'New York', :quarter => 'Q4', :sales => 40)
42
+
43
+ You can then generate a pivot table like so...
44
+
45
+ p = PivotTable::Table.new do |p|
46
+ p.data = my_data
47
+ p.column = :quarter
48
+ p.row = :city
49
+ p.pivot_on = :sales
50
+ end
51
+ p.generate
52
+
53
+ ...which will give you a hash that looks like this...
54
+
55
+ {
56
+ :headers => ['', 'Q1', 'Q2', 'Q3', 'Q4', 'Total'],
57
+ :rows => [
58
+ ['London', 100, 200, 300, 400, 1000],
59
+ ['New York', 10, 20, 30, 40, 100]
60
+ ],
61
+ :totals => ['Total', 110, 220, 330, 440, 1100]
62
+ }
63
+
64
+ ...which makes it easy-peasy to print a pivot table that looks like this...
65
+
66
+ Q1 Q2 Q3 Q4 Total
67
+ London 100 200 300 400 1000
68
+ New York 10 20 30 40 100
69
+ Total 110 220 330 440 1100
70
+
71
+ Ah, that's better.
72
+
73
+ Still to come
74
+ -------------
75
+
76
+ 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
+ Feel free to fork and/or suggest new features.
78
+
79
+ Contributing to PivotTable
80
+ ---------------------
81
+
82
+ If you want to contribute:
83
+
84
+ * Check out the latest master to make sure the feature hasn’t been implemented or the bug hasn’t been fixed yet
85
+ * Check out the issue tracker to make sure someone already hasn’t requested it and/or contributed it
86
+ * Fork the project
87
+ * Start a feature/bugfix branch
88
+ * Commit and push until you are happy with your contribution
89
+ * Make sure to add tests for it. This is important so I don’t break it in a future version unintentionally.
90
+ * Please try not to mess with the Rakefile, version, or history.
91
+
92
+ Copyright
93
+ ---------
94
+
95
+ Copyright (c) 2011 Ed James. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler/gem_tasks'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,6 @@
1
+ $: << File.dirname(__FILE__)
2
+ require "pivot_table/table"
3
+
4
+ module PivotTable
5
+ VERSION = "0.0.3"
6
+ end
@@ -0,0 +1,73 @@
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
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/pivot_table', __FILE__)
3
+ require 'base64'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "pivot_table"
7
+ s.version = PivotTable::VERSION
8
+ s.authors = ["Ed James"]
9
+ s.email = Base64.decode64("ZWQuamFtZXMuZW1haWxAZ21haWwuY29t\n")
10
+ s.homepage = "https://github.com/edjames/pivot_table"
11
+ s.summary = "pivot_table-#{s.version}"
12
+ s.description = "Transform an ActiveRecord-ish data set into pivot table"
13
+
14
+ s.platform = Gem::Platform::RUBY
15
+
16
+ s.rubyforge_project = "pivot_table"
17
+ s.rubygems_version = ">= 1.6.1"
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ["lib"]
22
+ end
@@ -0,0 +1,120 @@
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
@@ -0,0 +1,14 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rubygems'
4
+ require 'rspec'
5
+ require 'pivot_table'
6
+ require 'ostruct'
7
+
8
+ # Requires supporting files with custom matchers and macros, etc,
9
+ # in ./support/ and its subdirectories.
10
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
11
+
12
+ RSpec.configure do |config|
13
+
14
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pivot_table
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ed James
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-08-29 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: Transform an ActiveRecord-ish data set into pivot table
15
+ email: ed.james.email@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - .gitignore
21
+ - .rspec
22
+ - .rvmrc
23
+ - CHANGELOG.md
24
+ - Gemfile
25
+ - LICENSE
26
+ - README.md
27
+ - Rakefile
28
+ - lib/pivot_table.rb
29
+ - lib/pivot_table/table.rb
30
+ - pivot_table.gemspec
31
+ - spec/pivot_table/table_spec.rb
32
+ - spec/spec_helper.rb
33
+ homepage: https://github.com/edjames/pivot_table
34
+ licenses: []
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ! '>='
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubyforge_project: pivot_table
53
+ rubygems_version: 1.8.10
54
+ signing_key:
55
+ specification_version: 3
56
+ summary: pivot_table-0.0.3
57
+ test_files: []