to_excel 1.0
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/README.rdoc +87 -0
- data/lib/to_excel.rb +40 -0
- data/test/to_excel_helper.rb +49 -0
- data/test/to_excel_test.rb +104 -0
- metadata +63 -0
data/README.rdoc
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
= Introduction
|
2
|
+
I first used the good "arydjmal/to_xls" plugin but it doesn't allow collections of multiples objects from different classes and can't export
|
3
|
+
associations attributes of a class.
|
4
|
+
|
5
|
+
With to_excel gem you can export multiples collections of objects of different classes, moreover you have the possibility of
|
6
|
+
exporting associations's attributes (recursively).
|
7
|
+
|
8
|
+
= Usage
|
9
|
+
|
10
|
+
== Basic usage
|
11
|
+
@users = User.all
|
12
|
+
|
13
|
+
==== With one collection. All columns are exported with default humanized attributes names for excel columns :
|
14
|
+
|
15
|
+
[@users].to_excel
|
16
|
+
|
17
|
+
==== If you don't want excel header :
|
18
|
+
|
19
|
+
@users.to_excel headers => false
|
20
|
+
|
21
|
+
==== Personalized header
|
22
|
+
|
23
|
+
header = ['User Id', 'User name']
|
24
|
+
[@users].to_excel(:map => { User => [:id,:name], :headers => header)
|
25
|
+
|
26
|
+
|
27
|
+
==== To restrict attributes :
|
28
|
+
|
29
|
+
[@users].to_excel(:map => {User => [:age, :id, :name]}) #User is the class of users objects.
|
30
|
+
|
31
|
+
|
32
|
+
== Multiples collections of different classes
|
33
|
+
Suppose you have two classes : User and Computer, with Computer has_one :user
|
34
|
+
|
35
|
+
@users = [User.new(:name => 'Dupont', :age => 25)]
|
36
|
+
@computers =[Computer.new(:brand => 'Apple', :portable => true, :user => @users.first)]
|
37
|
+
|
38
|
+
==== You can export in the same file users and computers (in this order)
|
39
|
+
|
40
|
+
[@users, @computers].to_excel(:map => { User => [:id,:name], Computer => [:id, :brand,]})
|
41
|
+
|
42
|
+
==== You can export nested attributes, for example name of user of computer
|
43
|
+
In order to export the name column of the user of the computer, include [:user,:name] in the attribute list of key Computer :
|
44
|
+
|
45
|
+
[@users, @computers].to_excel(:map => { User => [:id,:name], Computer => [:id, :brand, [:user, :name]]})
|
46
|
+
|
47
|
+
|
48
|
+
==== You can export value of a instance method
|
49
|
+
|
50
|
+
Suppose Computer class has an instance method macintosh?
|
51
|
+
You can export the column values as if it was an attribute :
|
52
|
+
|
53
|
+
[@computers].to_excel(:map => {Computer => [:id, :macintosh?]})
|
54
|
+
|
55
|
+
|
56
|
+
==== Controller side
|
57
|
+
|
58
|
+
class UsersController < ....
|
59
|
+
|
60
|
+
....
|
61
|
+
send_data [@users, @computers].to_excel(:map => { User => [:id,:name], Computer => [:id, :brand,]})
|
62
|
+
|
63
|
+
....
|
64
|
+
end
|
65
|
+
|
66
|
+
If you want to use it with respond_to, you may have to include in initializes/mime_types :
|
67
|
+
|
68
|
+
Mime::Type.register "application/vnd.ms-excel", :xls
|
69
|
+
|
70
|
+
|
71
|
+
== Dependencies
|
72
|
+
|
73
|
+
No dependency.
|
74
|
+
|
75
|
+
== Install
|
76
|
+
|
77
|
+
sudo gem install to_excel
|
78
|
+
|
79
|
+
|
80
|
+
|
81
|
+
Copyright © 2010 Philippe Cantin, released under the MIT license.
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
|
data/lib/to_excel.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
class Array
|
2
|
+
def to_excel(options = {})
|
3
|
+
output = '<?xml version="1.0" encoding="UTF-8"?><Workbook xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40" xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office"><Worksheet ss:Name="Sheet1"><Table>'
|
4
|
+
|
5
|
+
if self.first.is_a?(Array) && self.first.any?
|
6
|
+
unless options[:headers] == false
|
7
|
+
output << "<Row>"
|
8
|
+
|
9
|
+
if options[:headers].is_a? Array
|
10
|
+
options[:headers].each do |title|
|
11
|
+
output << "<Cell><Data ss:Type=\"String\">#{title}</Data></Cell>"
|
12
|
+
end
|
13
|
+
else
|
14
|
+
klasses = self.map(&:first).map(&:class)
|
15
|
+
klasses.each do |klass|
|
16
|
+
options[:map][klass].each do |attribute|
|
17
|
+
output << "<Cell><Data ss:Type=\"String\">#{klass.human_attribute_name(attribute)}</Data></Cell>"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
output << "</Row>"
|
23
|
+
end
|
24
|
+
|
25
|
+
(0..self.map(&:length).max - 1).each do |n|
|
26
|
+
items = self.map {|collection| collection[n] || collection.first.class.new}
|
27
|
+
output << "<Row>"
|
28
|
+
items.each do |item|
|
29
|
+
options[:map][item.class].each do |column|
|
30
|
+
value = column.is_a?(Array) ? column.inject(item){|result,column| result.send(column) if result} : item.send(column)
|
31
|
+
output << "<Cell><Data ss:Type=\"#{value.is_a?(Integer) ? 'Number' : 'String'}\">#{value}</Data></Cell>"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
output << "</Row>"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
output << '</Table></Worksheet></Workbook>'
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
begin
|
2
|
+
require 'rubygems'
|
3
|
+
require 'test/unit'
|
4
|
+
require 'active_support'
|
5
|
+
require 'to_excel'
|
6
|
+
end
|
7
|
+
|
8
|
+
class User
|
9
|
+
COLUMNS = %w(id name age)
|
10
|
+
|
11
|
+
attr_accessor *COLUMNS
|
12
|
+
|
13
|
+
def self.human_attribute_name(attribute)
|
14
|
+
attribute.to_s.humanize
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(params={})
|
18
|
+
params.each { |key, value| self.send("#{key}=", value); }
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def attributes
|
23
|
+
COLUMNS.inject({}) { |attributes, attribute| attributes.merge(attribute => send(attribute)) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def is_old?
|
27
|
+
age > 40
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Computer
|
32
|
+
COLUMNS = %w(id brand portable user)
|
33
|
+
|
34
|
+
attr_accessor *COLUMNS
|
35
|
+
|
36
|
+
def self.human_attribute_name(attribute)
|
37
|
+
attribute.to_s.humanize
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize(params={})
|
41
|
+
params.each { |key, value| self.send("#{key}=", value); }
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def attributes
|
46
|
+
COLUMNS.inject({}) { |attributes, attribute| attributes.merge(attribute => send(attribute)) }
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'to_excel_helper'
|
2
|
+
|
3
|
+
class ToExcelTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@users = [
|
7
|
+
User.new(:id => 1, :name => 'Dupont', :age => 25),
|
8
|
+
User.new(:id => 2, :name => 'Martin', :age => 22)
|
9
|
+
]
|
10
|
+
@computers =[
|
11
|
+
Computer.new(:id => 1, :brand => 'Apple', :portable => true, :user => @users.first),
|
12
|
+
Computer.new(:id => 2, :brand => 'Dell', :portable => false, :user => @users[1])
|
13
|
+
]
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_should_be_array_of_arrays_not_empty
|
17
|
+
assert_equal build_document(nil), [].to_excel
|
18
|
+
assert_equal build_document(nil), [[]].to_excel
|
19
|
+
assert_equal build_document(nil), @users.to_excel
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_with_no_headers
|
23
|
+
document = build_document(
|
24
|
+
row(cell('Number', '25'), cell('Number', '1'), cell('String', 'Dupont')),
|
25
|
+
row(cell('Number', '22'), cell('Number', '2'), cell('String', 'Martin'))
|
26
|
+
)
|
27
|
+
assert_equal document, [@users].to_excel(:headers => false, :map => {User => [:age, :id, :name]})
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_with_methods
|
31
|
+
document = build_document(
|
32
|
+
row(cell('String', 'Age'), cell('String', 'Id'), cell('String', 'Name'), cell('String', 'Is old?')),
|
33
|
+
row(cell('Number', '25'), cell('Number', '1'), cell('String', 'Dupont'), cell('String', 'false')),
|
34
|
+
row(cell('Number', '22'), cell('Number', '2'), cell('String', 'Martin'), cell('String', 'false'))
|
35
|
+
)
|
36
|
+
assert_equal document, [@users].to_excel(:map => { User => [:age, :id, :name, :is_old?]})
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_with_nested_attribute
|
40
|
+
document = build_document(
|
41
|
+
row(cell('String', 'Id'), cell('String', 'Username')),
|
42
|
+
row(cell('Number', '1'), cell('String', 'Dupont')),
|
43
|
+
row(cell('Number', '2'), cell('String', 'Martin'))
|
44
|
+
)
|
45
|
+
assert_equal document, [@computers].to_excel(:map => { Computer => [:id, [:user, :name]]})
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_with_two_collections
|
49
|
+
document = build_document(
|
50
|
+
row(cell('String', 'Id'), cell('String', 'Name'), cell('String', 'Id'), cell('String', 'Brand')),
|
51
|
+
row(cell('Number', '1'), cell('String', 'Dupont'), cell('Number', '1'), cell('String', 'Apple')),
|
52
|
+
row(cell('Number', '2'), cell('String', 'Martin'), cell('Number', '2'), cell('String', 'Dell'))
|
53
|
+
)
|
54
|
+
assert_equal document, [@users, @computers].to_excel(:map => { User => [:id,:name], Computer => [:id,:brand]})
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_with_one_collection
|
58
|
+
document = build_document(
|
59
|
+
row(cell('String', 'Id'), cell('String', 'Username')),
|
60
|
+
row(cell('Number', '1'), cell('String', 'Dupont')),
|
61
|
+
row(cell('Number', '2'), cell('String', 'Martin'))
|
62
|
+
)
|
63
|
+
assert_equal document, [@computers].to_excel(:map => {Computer => [:id,[:user, :name]]})
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_should_render_empty_cells_unless_collections_of_same_size
|
67
|
+
@users << User.new(:id => 3, :name => 'Durand', :age => 23)
|
68
|
+
document = build_document(
|
69
|
+
row(cell('String', 'Id'), cell('String', 'Name'), cell('String', 'Id'), cell('String', 'Brand')),
|
70
|
+
row(cell('Number', '1'), cell('String', 'Dupont'), cell('Number', '1'), cell('String', 'Apple')),
|
71
|
+
row(cell('Number', '2'), cell('String', 'Martin'), cell('Number', '2'), cell('String', 'Dell')),
|
72
|
+
row(cell('Number', '3'), cell('String', 'Durand'), cell('String', ''), cell('String', ''))
|
73
|
+
)
|
74
|
+
assert_equal document, [@users, @computers].to_excel(:map => { User => [:id,:name], Computer => [:id,:brand]})
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_with_own_header
|
78
|
+
document = build_document(
|
79
|
+
row(cell('String', 'User Id'), cell('String', 'Nom'), cell('String', 'Computer Id'), cell('String', 'Marque')),
|
80
|
+
row(cell('Number', '1'), cell('String', 'Dupont'), cell('Number', '1'), cell('String', 'Apple')),
|
81
|
+
row(cell('Number', '2'), cell('String', 'Martin'), cell('Number', '2'), cell('String', 'Dell'))
|
82
|
+
)
|
83
|
+
header = ['User Id', 'Nom', 'Computer Id', 'Marque']
|
84
|
+
assert_equal document, [@users, @computers].to_excel(:map => { User => [:id,:name], Computer => [:id,:brand]}, :headers => header)
|
85
|
+
end
|
86
|
+
#TODO generate default map from class columns if no map option passed
|
87
|
+
def test_with_no_map_option
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def build_document(*rows)
|
94
|
+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?><Workbook xmlns:x=\"urn:schemas-microsoft-com:office:excel\" xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\" xmlns:html=\"http://www.w3.org/TR/REC-html40\" xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\" xmlns:o=\"urn:schemas-microsoft-com:office:office\"><Worksheet ss:Name=\"Sheet1\"><Table>#{rows}</Table></Worksheet></Workbook>"
|
95
|
+
end
|
96
|
+
|
97
|
+
def row(*cells)
|
98
|
+
"<Row>#{cells}</Row>"
|
99
|
+
end
|
100
|
+
|
101
|
+
def cell(type, content)
|
102
|
+
"<Cell><Data ss:Type=\"#{type}\">#{content}</Data></Cell>"
|
103
|
+
end
|
104
|
+
end
|
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: to_excel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 0
|
8
|
+
version: "1.0"
|
9
|
+
platform: ruby
|
10
|
+
authors:
|
11
|
+
- Philippe Cantin
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
|
16
|
+
date: 2010-05-01 00:00:00 +02:00
|
17
|
+
default_executable:
|
18
|
+
dependencies: []
|
19
|
+
|
20
|
+
description: Export ruby objects to excel file
|
21
|
+
email:
|
22
|
+
executables: []
|
23
|
+
|
24
|
+
extensions: []
|
25
|
+
|
26
|
+
extra_rdoc_files:
|
27
|
+
- README.rdoc
|
28
|
+
files:
|
29
|
+
- lib/to_excel.rb
|
30
|
+
- README.rdoc
|
31
|
+
has_rdoc: true
|
32
|
+
homepage: http://github.com/anoiaque/to_excel
|
33
|
+
licenses: []
|
34
|
+
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
segments:
|
52
|
+
- 0
|
53
|
+
version: "0"
|
54
|
+
requirements: []
|
55
|
+
|
56
|
+
rubyforge_project:
|
57
|
+
rubygems_version: 1.3.6
|
58
|
+
signing_key:
|
59
|
+
specification_version: 3
|
60
|
+
summary: Export ruby objects to excel file. Allow many collections of different classpec. Can include associations attributespec.
|
61
|
+
test_files:
|
62
|
+
- test/to_excel_helper.rb
|
63
|
+
- test/to_excel_test.rb
|