Skip to content

Commit

Permalink
half-implemented tests. separated rendering from Array class
Browse files Browse the repository at this point in the history
  • Loading branch information
kikito committed Apr 17, 2011
1 parent ca0770c commit 0985819
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 101 deletions.
2 changes: 1 addition & 1 deletion README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This gem transform an Array or Hash into a excel file using the spreadsheet gem.
When invoked on a Hash each value is output to a separate sheet named after the corresponding key. Options for each sheet can be specified using key/values in the options hash.

{ :users => @users, :posts => @posts }.to_xls
{ :users => @users, :posts => @posts }.to_xls(:users => { :columns => [:name, :role] }, :posts => { :headers => [:title, :content] })
{ :users => @users, :posts => @posts }.to_xls(:users => { :columns => [:name, :role] }, :posts => { :columns => [:title, :content] })

If you want to use the file, for example in rails, you have two options. The first one is saving it first, and using send_file:

Expand Down
80 changes: 3 additions & 77 deletions lib/to_xls.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'rubygems'
require 'spreadsheet'
require 'stringio'
require 'to_xls/array_renderer.rb'

class Hash
def to_xls(options = {})
Expand All @@ -25,90 +26,15 @@ def to_xls_data(options = {})
class Array
# Options for to_xls: columns, name, header, sheet
def to_xls(options = {})
sheet = options[:sheet]
unless sheet
book = Spreadsheet::Workbook.new
sheet = book.create_worksheet
sheet.name = options[:name] || 'Sheet 1'
end

if self.any?
columns = get_columns(options)

if columns.is_a?(Array) && columns.any?
line = 0

unless options[:headers] == false
if options[:headers].is_a?(Array)
sheet.row(0).concat options[:headers].collect(&:to_s)
else
aux_headers_to_xls(self.first, columns, sheet.row(0))
end
line = 1
end

self.each do |item|
row = sheet.row(line)
columns.each {|column| aux_to_xls(item, column, row)}
line += 1
end
end
end

return book || sheet
renderer = ToXls::ArrayRenderer.new(self, options)
return renderer.render
end

def to_xls_data(options = {})
data = StringIO.new('')
self.to_xls(options).write(data)
return data.string
end

private

def get_columns(options)
columns = options[:columns]
raise ArgumentError, ":columns (#{columns}) must be an array or nil" unless (columns.nil? || columns.is_a?(Array))
if !columns && can_get_columns_from_first_element?
columns = get_columns_from_first_element
end
return columns
end

def can_get_columns_from_first_element?
self.first &&
self.first.respond_to?(:attributes) &&
self.first.attributes.respond_to?(:keys) &&
self.first.attributes.keys.is_a?(Array)
end

def get_columns_from_first_element
self.first.attributes.keys.sort
end


def aux_to_xls(item, column, row)
if item.nil?
row.push(nil)
elsif column.is_a?(String) or column.is_a?(Symbol)
row.push(item.send(column))
elsif column.is_a?(Hash)
column.each{|key, values| aux_to_xls(item.send(key), values, row)}
elsif column.is_a?(Array)
column.each{|value| aux_to_xls(item, value, row)}
end
end

def aux_headers_to_xls(item, column, row)
if item.nil?
row.push(nil)
elsif column.is_a?(String) or column.is_a?(Symbol)
row.push(column.to_s)
elsif column.is_a?(Hash)
column.each{|key, values| aux_headers_to_xls(item.send(key), values, row)}
elsif column.is_a?(Array)
column.each{|value| aux_headers_to_xls(item, value, row)}
end
end

end
106 changes: 106 additions & 0 deletions lib/to_xls/array_renderer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@

module ToXls

class ArrayRenderer
def initialize(array, options)
@array = array
@options = options
end

def render
fill_up_sheet
return @book || @sheet
end

def fill_up_sheet
if columns.any?
row_index = 0

if headers_should_be_included?
sheet.row(0).concat headers
row_index = 1
end

@array.each do |item|
row = sheet.row(row_index)
columns.each {|column| aux_to_xls(item, column, row)}
row_index += 1
end
end
end

def sheet
return @sheet if @sheet
@sheet = @options[:sheet]
unless @sheet
@book = Spreadsheet::Workbook.new
@sheet = @book.create_worksheet
@sheet.name = @options[:name] || 'Sheet 1'
end
@sheet
end

def columns
return @columns if @columns
@columns = @options[:columns]
raise ArgumentError, ":columns (#{columns}) must be an array or nil" unless (@columns.nil? || @columns.is_a?(Array))
if !@columns && can_get_columns_from_first_element?
@columns = get_columns_from_first_element
end
@columns = (@columns || []).collect{|c| c.to_s}
end

def can_get_columns_from_first_element?
@array.first &&
@array.first.respond_to?(:attributes) &&
@array.first.attributes.respond_to?(:keys) &&
@array.first.attributes.keys.is_a?(Array)
end

def get_columns_from_first_element
@array.first.attributes.keys.sort.collect
end

def headers
return @headers if @headers
@headers = @options[:headers]
if @headers
raise ArgumentError, ":headers (#{@headers}) must be an array or nil" unless @headers.is_a? Array
@headers = @headers.collect{|h| h.to_s}
else
@headers = columns
end
@headers
end

def headers_should_be_included?
@options[:headers] != false
end


def aux_headers_to_xls(item, column, row)
if item.nil?
row.push(nil)
elsif column.is_a?(String) or column.is_a?(Symbol)
row.push(column.to_s)
elsif column.is_a?(Hash)
column.each{|key, values| aux_headers_to_xls(item.send(key), values, row)}
elsif column.is_a?(Array)
column.each{|value| aux_headers_to_xls(item, value, row)}
end
end

def aux_to_xls(item, column, row)
if item.nil?
row.push(nil)
elsif column.is_a?(String) or column.is_a?(Symbol)
row.push(item.send(column))
elsif column.is_a?(Hash)
column.each{|key, values| aux_to_xls(item.send(key), values, row)}
elsif column.is_a?(Array)
column.each{|value| aux_to_xls(item, value, row)}
end
end

end
end
106 changes: 83 additions & 23 deletions spec/array_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,29 @@

describe Array do

def mock_user(name, age, email)
attributes = { 'name' => name, 'age' => age, 'email' => email }

def mock_model(name, attributes)
attributes[:attributes] = attributes.clone
mock(name, attributes)
end

def mock_company(name, address)
mock_model( name, 'name' => name, 'address' => address )
end

def mock_user(name, age, email, company)
user = mock_model(name, 'name' => name, 'age' => age, 'email' => email)
user.stub!(:company).and_return(company)
user
end

def mock_users
[ mock_user('Peter', 20, 'peter@gmail.com'),
mock_user('John', 25, 'john@gmail.com'),
mock_user('Day9', 27, 'day9@day9tv.com')
acme = mock_company('Acme', 'One Road')
eads = mock_company('EADS', 'Another Road')

[ mock_user('Peter', 20, 'peter@gmail.com', acme),
mock_user('John', 25, 'john@gmail.com', acme),
mock_user('Day9', 27, 'day9@day9tv.com', eads)
]
end

Expand All @@ -24,25 +37,72 @@ def check_sheet(sheet, array)
it "should throw no error without data" do
lambda { [].to_xls }.should_not raise_error
end
it "should throw no error without columns" do
lambda { [1,2,3].to_xls }.should_not raise_error
end
it "should throw an error if columns exists but it isn't an array" do
lambda { [1,2,3].to_xls(:columns => :foo) }.should raise_error
end
it "should use the attribute keys as columns if it exists" do
xls = mock_users.to_xls
check_sheet( xls.worksheets.first,
[ ['age', 'email', 'name'],
[ 20, 'peter@gmail.com', 'Peter'],
[ 25, 'john@gmail.com', 'John'],
[ 27, 'day9@day9tv.com', 'Day9']
]
)


describe ":columns option" do
it "should throw no error without columns" do
lambda { [1,2,3].to_xls }.should_not raise_error
end
it "should throw an error if columns exists but it isn't an array" do
lambda { [1,2,3].to_xls(:columns => :foo) }.should raise_error
end
it "should use the attribute keys as columns if it exists" do
xls = mock_users.to_xls
check_sheet( xls.worksheets.first,
[ ['age', 'email', 'name'],
[ 20, 'peter@gmail.com', 'Peter'],
[ 25, 'john@gmail.com', 'John'],
[ 27, 'day9@day9tv.com', 'Day9']
]
)
end
it "should allow re-sorting of the columns by using the :columns option" do
xls = mock_users.to_xls(:columns => [:name, :email, :age])
check_sheet( xls.worksheets.first,
[ ['name', 'email', 'age'],
['Peter', 'peter@gmail.com', 20],
['John', 'john@gmail.com', 25],
['Day9', 'day9@day9tv.com', 27]
]
)
end

it "should work properly when you provide it with both data and column names" do
xls = [1,2,3].to_xls(:columns => [:to_s])
check_sheet( xls.worksheets.first, [ ['to_s'], ['1'], ['2'], ['3'] ] )
end
end

it "should work properly when you provide it with both data and column names" do
xls = [1,2,3].to_xls(:columns => [:to_s])
check_sheet( xls.worksheets.first, [ ['to_s'], ['1'], ['2'], ['3'] ] )
describe ":headers option" do

it "should use the headers option if it exists" do
xls = mock_users.to_xls(
:columns => [:name, :email, :age],
:headers => ['Nombre', 'Correo', 'Edad']
)
check_sheet( xls.worksheets.first,
[ ['Nombre', 'Correo', 'Edad'],
['Peter', 'peter@gmail.com', 20],
['John', 'john@gmail.com', 25],
['Day9', 'day9@day9tv.com', 27]
]
)
end

it "should include no headers if the headers option is false" do
xls = mock_users.to_xls(
:columns => [:name, :email, :age],
:headers => false
)
check_sheet( xls.worksheets.first,
[ ['Peter', 'peter@gmail.com', 20],
['John', 'john@gmail.com', 25],
['Day9', 'day9@day9tv.com', 27]
]
)
end

end


end

0 comments on commit 0985819

Please sign in to comment.