Skip to content
Browse files

* to_excel 1.0

  • Loading branch information...
0 parents commit 94964df4da6c19c86b13cc0b58933bfe8ea5eff2 anoiaque committed Apr 23, 2010
Showing with 193 additions and 0 deletions.
  1. +40 −0 lib/to_excel.rb
  2. +49 −0 test/to_excel_helper.rb
  3. +104 −0 test/to_excel_test.rb
40 lib/to_excel.rb
@@ -0,0 +1,40 @@
+class Array
+ def to_excel(options = {})
+ 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>'
+
+ if self.first.is_a?(Array) && self.first.any?
+ unless options[:headers] == false
+ output << "<Row>"
+
+ if options[:headers].is_a? Array
+ options[:headers].each do |title|
+ output << "<Cell><Data ss:Type=\"String\">#{title}</Data></Cell>"
+ end
+ else
+ klasses = self.map(&:first).map(&:class)
+ klasses.each do |klass|
+ options[:map][klass].each do |attribute|
+ output << "<Cell><Data ss:Type=\"String\">#{klass.human_attribute_name(attribute)}</Data></Cell>"
+ end
+ end
+ end
+
+ output << "</Row>"
+ end
+
+ (0..self.map(&:length).max - 1).each do |n|
+ items = self.map {|collection| collection[n] || collection.first.class.new}
+ output << "<Row>"
+ items.each do |item|
+ options[:map][item.class].each do |column|
+ value = column.is_a?(Array) ? column.inject(item){|result,column| result.send(column) if result} : item.send(column)
+ output << "<Cell><Data ss:Type=\"#{value.is_a?(Integer) ? 'Number' : 'String'}\">#{value}</Data></Cell>"
+ end
+ end
+ output << "</Row>"
+ end
+ end
+
+ output << '</Table></Worksheet></Workbook>'
+ end
+end
49 test/to_excel_helper.rb
@@ -0,0 +1,49 @@
+begin
+ require 'rubygems'
+ require 'test/unit'
+ require 'active_support'
+ require 'to_excel'
+end
+
+class User
+ COLUMNS = %w(id name age)
+
+ attr_accessor *COLUMNS
+
+ def self.human_attribute_name(attribute)
+ attribute.to_s.humanize
+ end
+
+ def initialize(params={})
+ params.each { |key, value| self.send("#{key}=", value); }
+ self
+ end
+
+ def attributes
+ COLUMNS.inject({}) { |attributes, attribute| attributes.merge(attribute => send(attribute)) }
+ end
+
+ def is_old?
+ age > 40
+ end
+end
+
+class Computer
+ COLUMNS = %w(id brand portable user)
+
+ attr_accessor *COLUMNS
+
+ def self.human_attribute_name(attribute)
+ attribute.to_s.humanize
+ end
+
+ def initialize(params={})
+ params.each { |key, value| self.send("#{key}=", value); }
+ self
+ end
+
+ def attributes
+ COLUMNS.inject({}) { |attributes, attribute| attributes.merge(attribute => send(attribute)) }
+ end
+
+end
104 test/to_excel_test.rb
@@ -0,0 +1,104 @@
+require 'to_excel_helper'
+
+class ToExcelTest < Test::Unit::TestCase
+
+ def setup
+ @users = [
+ User.new(:id => 1, :name => 'Dupont', :age => 25),
+ User.new(:id => 2, :name => 'Martin', :age => 22)
+ ]
+ @computers =[
+ Computer.new(:id => 1, :brand => 'Apple', :portable => true, :user => @users.first),
+ Computer.new(:id => 2, :brand => 'Dell', :portable => false, :user => @users[1])
+ ]
+ end
+
+ def test_should_be_array_of_arrays_not_empty
+ assert_equal build_document(nil), [].to_excel
+ assert_equal build_document(nil), [[]].to_excel
+ assert_equal build_document(nil), @users.to_excel
+ end
+
+ def test_with_no_headers
+ document = build_document(
+ row(cell('Number', '25'), cell('Number', '1'), cell('String', 'Dupont')),
+ row(cell('Number', '22'), cell('Number', '2'), cell('String', 'Martin'))
+ )
+ assert_equal document, [@users].to_excel(:headers => false, :map => {User => [:age, :id, :name]})
+ end
+
+ def test_with_methods
+ document = build_document(
+ row(cell('String', 'Age'), cell('String', 'Id'), cell('String', 'Name'), cell('String', 'Is old?')),
+ row(cell('Number', '25'), cell('Number', '1'), cell('String', 'Dupont'), cell('String', 'false')),
+ row(cell('Number', '22'), cell('Number', '2'), cell('String', 'Martin'), cell('String', 'false'))
+ )
+ assert_equal document, [@users].to_excel(:map => { User => [:age, :id, :name, :is_old?]})
+ end
+
+ def test_with_nested_attribute
+ document = build_document(
+ row(cell('String', 'Id'), cell('String', 'Username')),
+ row(cell('Number', '1'), cell('String', 'Dupont')),
+ row(cell('Number', '2'), cell('String', 'Martin'))
+ )
+ assert_equal document, [@computers].to_excel(:map => { Computer => [:id, [:user, :name]]})
+ end
+
+ def test_with_two_collections
+ document = build_document(
+ row(cell('String', 'Id'), cell('String', 'Name'), cell('String', 'Id'), cell('String', 'Brand')),
+ row(cell('Number', '1'), cell('String', 'Dupont'), cell('Number', '1'), cell('String', 'Apple')),
+ row(cell('Number', '2'), cell('String', 'Martin'), cell('Number', '2'), cell('String', 'Dell'))
+ )
+ assert_equal document, [@users, @computers].to_excel(:map => { User => [:id,:name], Computer => [:id,:brand]})
+ end
+
+ def test_with_one_collection
+ document = build_document(
+ row(cell('String', 'Id'), cell('String', 'Username')),
+ row(cell('Number', '1'), cell('String', 'Dupont')),
+ row(cell('Number', '2'), cell('String', 'Martin'))
+ )
+ assert_equal document, [@computers].to_excel(:map => {Computer => [:id,[:user, :name]]})
+ end
+
+ def test_should_render_empty_cells_unless_collections_of_same_size
+ @users << User.new(:id => 3, :name => 'Durand', :age => 23)
+ document = build_document(
+ row(cell('String', 'Id'), cell('String', 'Name'), cell('String', 'Id'), cell('String', 'Brand')),
+ row(cell('Number', '1'), cell('String', 'Dupont'), cell('Number', '1'), cell('String', 'Apple')),
+ row(cell('Number', '2'), cell('String', 'Martin'), cell('Number', '2'), cell('String', 'Dell')),
+ row(cell('Number', '3'), cell('String', 'Durand'), cell('String', ''), cell('String', ''))
+ )
+ assert_equal document, [@users, @computers].to_excel(:map => { User => [:id,:name], Computer => [:id,:brand]})
+ end
+
+ def test_with_own_header
+ document = build_document(
+ row(cell('String', 'User Id'), cell('String', 'Nom'), cell('String', 'Computer Id'), cell('String', 'Marque')),
+ row(cell('Number', '1'), cell('String', 'Dupont'), cell('Number', '1'), cell('String', 'Apple')),
+ row(cell('Number', '2'), cell('String', 'Martin'), cell('Number', '2'), cell('String', 'Dell'))
+ )
+ header = ['User Id', 'Nom', 'Computer Id', 'Marque']
+ assert_equal document, [@users, @computers].to_excel(:map => { User => [:id,:name], Computer => [:id,:brand]}, :headers => header)
+ end
+ #TODO generate default map from class columns if no map option passed
+ def test_with_no_map_option
+
+ end
+
+private
+
+ def build_document(*rows)
+ "<?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>"
+ end
+
+ def row(*cells)
+ "<Row>#{cells}</Row>"
+ end
+
+ def cell(type, content)
+ "<Cell><Data ss:Type=\"#{type}\">#{content}</Data></Cell>"
+ end
+end

0 comments on commit 94964df

Please sign in to comment.
Something went wrong with that request. Please try again.