Update:
I've released a gem for rendering .xls and .xslx from Ruby on RailsA while ago I was looking for something that allows generating simple xlsx files from Ruby.
I soon found simple_xlsx. Unlike other gems that let you generate Excel files, this one does not have any Windows dependencies, so the choice was easy.
Generating xlsx file procedurally worked out of the box, but I wanted the xlsx to become part of the view.
After reading Yehuda's post on custom renderers I decided to write one that converts HTML to XLSX.
The goal is to be able to render both xlsx and html with something like this:
class ContactsController < ApplicationController
respond_to :xlsx, :html
def index
@contacts = current_user.contacts
# render either xlsx or html depending on request.format:
respond_with(@contacts)
end
end
Moreover, it would be nice to be able to generate them from the same partial, e.g. like the one below:
-# _contacts.haml
%table
%caption My contacts
%thead
%tr
%th Email
%th Phone
%tbody
- contacts.each do |contact|
%tr
%td= contact.email
%td= contact.phone_number
In contacts/index.html.haml you would have something like:
= render 'menu'
= render '_contacts', :contacts => @contacts
And in contacts/index.xlsx.haml same, but without the navigation stuff:
= render '_contacts', :contacts => @contacts
Turned out, it is easy to do in Rails 3.
After some hours and help from SO, I finished a simple implementation:
Add the mime type to config/initializers/mime_types.rb:
Mime::Type.register "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", :xlsxAdd the renderer, and register a responder:
require 'action_controller/metal/renderers'
require 'action_controller/metal/responder'
# This will let us do thing like `render :xlsx => 'index'`
# This is also how Rails internally implements its :json and :xml renderers
# Rarely used, nevertheless public API
ActionController::Renderers.add :xlsx do |template, options|
output = Tempfile.new("#{template}.xslx")
html = render_to_string(template, options)
SimpleXlsx::Serializer.new(output) do |xlsx_doc|
Nokogiri::XML::Document.parse(html).xpath('/table').each do |xml_table|
xlsx_doc.add_sheet(xml_table.css('caption').inner_text) do |sheet|
xml_table.css('tr').each do |row|
sheet.add_row(row.css('th,td').map(&:inner_text))
end
end
end
end
send_data File.open(output.path, 'rb', &:read), :type => :xlsx
end
# This sets up a default render call for when you do
# respond_to do |format|
# format.xlsx
# end
class ActionController::Responder
def to_xlsx
controller.render :xlsx => controller.action_name
end
end
Wow, that really is a dirty hack. Any way of keeping Simple Xlsx from throwing down into a file?
ReplyDeleteHi. The new version of my gem uses axlsx for generating xls, and that one nicely allows you to just read from a stream, see https://github.com/glebm/to_spreadsheet
ReplyDelete