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.
@@ -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
+
@@ -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