ActionView rcsv template handler
The way the rjs and builder template handlers work in rails is nice. I do a lot of work with csv for generating reports, and having all the csv generation code in the controller doesn't seem to fit the MVC paradigm. I dug in to how ActionView template handlers work in Rails 2.1 and came up with the following solution.
The code uses the FasterCSV gem so you'll need that installed. One gotcha i noticed is that if format.csv is before format.html in the respond_to block the request gets sent incorrectly.
Code
RAILS_ROOT/config/initializers/template_handlers.rb
require 'rcsv_template_handler'
ActionView::Template.register_template_handler :rcsv, ActionView::TemplateHandlers::RCSV
RAILS_ROOT/lib/rcsv_template_handler.rb
require 'fastercsv'
module ActionView
module TemplateHandlers
class RCSV < TemplateHandler
include Compilable
def self.line_offset
2
end
def compile(template)
content_type_handler = (@view.send!(:controller).respond_to?(:response) ? "controller.response" : "controller")
"#{content_type_handler}.content_type ||= Mime::CSV
" +
"if controller.request.env['HTTP_USER_AGENT'] =~ /msie/i
" +
" controller.headers['Pragma'] = 'public'
" +
" #{content_type_handler}.content_type ||= Mime::TEXT
" +
" controller.headers['Cache-Control'] = 'no-cache, must-revalidate, post-check=0, pre-check=0'
" +
" controller.headers['Expires'] = '0'
" +
"else
" +
" #{content_type_handler.content_type ||= Mime::CSV
" +
"end
" +
"csv_doc = ::FasterCSV.new(:row_sep = "
") do |csv|
#{template.source}
end"
end
def cache_fragment(block, name = {}, options = nil)
@view.fragment_for(block, name, options) do
eval('csv_doc', block.binding)
end
end
end
end
end
Usage
RAILS_ROOT/app/controllers/name_controller.rb
class NameController < ApplicationController
def action
@data = Data.find :all
respond_to do |format|
format.html # Render action.html.erb
format.csv # Render action.csv.rcsv
end
end
end
RAILS_ROOT/app/view/name/action.csv.rcsv
@data.each do |datum|
csv << datum
end