module Excel # Class designed to create a multiple-sheet workbook in Excel from ActiveRecord model objects class Workbook def initialize @worksheets = Array.new end # Add a sheet to the colleection of worksheets. # * sheetname (string) is the name of the worksheet tab # * objectType (string) is the type of object you're sending in. # # * objects is your collection of ActiveRecord objects. # # Here's an example # # @book = Book.find(:all) # @authors = Authors.find(:all) # addWorksheetFromActiveRecord "Books", "book", @book # addWorksheetFromActiveRecord "Authors", "author", @authors def addWorksheetFromActiveRecord(sheetname, objectType, objects) objects = [objects] unless objects.class == Array item = [sheetname.to_s, objectType.to_s, objects] @worksheets += [item] end # Add a sheet to the colleection of worksheets. # * sheetname (string) is the name of the worksheet tab # * array (Array) is an array of Hashes that contain the data you want to render. # ** It should be noted that the insertion order cannot be preserved. # # Here's an example # array = Array.new # item = OrderedHash.new # item["Name"] = "John Smith" # item["Department"] = "Accounts Payable" # item["Location"] = "Chicago" # item["Salary"] = "$42,032" # item["Title"] = "Junior Accountant" # array << item # addWorksheetFromArrayOfHashes("Accounting info", array) # # Or, a more appropriate solution # # books = Book.find(:all) # array = Array.new # for book in books # item = OrderedHash.new # item["Title"] = book.title # item["ISBN"] = book.isbn # item["Author"] = book.author.last_name # item["Category"] = book.category.name # item["Total Sales"] = book.sales.size # array << item # end # addWorksheetFromArrayOfHashes("Books info", array) # # This solution would allow you to export a more useful view of your data by being able to # export the values of your relationships to the Excel workbook. def addWorksheetFromArrayOfHashes(sheetname, array) item = [sheetname.to_s, 'array', array] @worksheets += [item] end # Returns the Excel workbook in XML format. # In the controller, set the content type appropriately to send this back. # # Example: # headers['Content-Type'] = "application/vnd.ms-excel" # render_text(e.build) def build buffer = "" xml = Builder::XmlMarkup.new(buffer) xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8" xml.Workbook({ 'xmlns' => "urn:schemas-microsoft-com:office:spreadsheet", 'xmlns:o' => "urn:schemas-microsoft-com:office:office", 'xmlns:x' => "urn:schemas-microsoft-com:office:excel", 'xmlns:html' => "http://www.w3.org/TR/REC-html40", 'xmlns:ss' => "urn:schemas-microsoft-com:office:spreadsheet" }) do xml.Styles do xml.Style 'ss:ID' => 'Default', 'ss:Name' => 'Normal' do xml.Alignment 'ss:Vertical' => 'Bottom' xml.Borders xml.Font 'ss:FontName' => 'Arial' xml.Interior xml.NumberFormat xml.Protection end xml.Style 'ss:ID' => 's22' do xml.NumberFormat 'ss:Format' => 'General Date' end end for object in @worksheets # use the << operator to prevent the < > and & characters from being converted. # this will concat them together. if object[1] =='array' xml << worksheetFromArray(object[0], object[2]) else xml << worksheet(object[0], object[1], object[2]) end end # for records end return xml.target! end private # renders an Excel worksheet. # Paramters: # # * sheetname is the name for the worksheet # * objectType is a string for the model type that you're exporting. For example: if you have a collection of Author objects, "author" would be your type. # * objects is a collection of ActiveRecord objects def worksheet (sheetname, objectType,objects) buffer ="" xm = Builder::XmlMarkup.new(buffer) # stream to the text buffer type = ActiveRecord::Base.const_get(objectType.classify) xm.Worksheet 'ss:Name' => sheetname do xm.Table do # Header xm.Row do for column in type.columns do xm.Cell do xm.Data column.human_name, 'ss:Type' => 'String' end end end # Rows for record in objects xm.Row do for column in type.columns do xm.Cell do xm.Data record.send(column.name), 'ss:Type' => 'String' end end end end # for end # table end #worksheet return xm.target! # retrieves the buffer end def worksheetFromArray(sheetname, array) buffer ="" xm = Builder::XmlMarkup.new(buffer) # stream to the text buffer xm.Worksheet 'ss:Name' => sheetname do xm.Table do #header xm.Row do for key in array[0].keys xm.Cell do xm.Data key, 'ss:Type' => 'String' end end #for end #row #data for item in array xm.Row do for value in item.values xm.Cell do xm.Data value, 'ss:Type' => 'String' end end end end end #table end #worksheet return xm.target! # retrieves the buffer end end end