I have an object that has a to_csv
method and I want to pass it to respond_with
to render csv from my controller. My code looks like this:
I've been struggling with the exact same problem. I might have found a solution.
I found some clues while reading the Renderers.add source code for :json and :xml (link is for Rails 3.0.10 code, 3.1 might have some changes already): https://github.com/rails/rails/blob/v3.0.10/actionpack/lib/action_controller/metal/renderers.rb
First, add a simple as_csv
method to your model definition:
class Modelname < ActiveRecord::Base
# ...
def as_csv
attributes
end
end
This can be anything, just make sure to return a hash with key/value pairs. A Hash works better than an Array, as with keys you're able to add a header row to the CSV output later on. The idea for as_csv
comes from Rails' as_json
method, which return a Ruby object that is used by to_json
to generate the actual JSON (text) output.
With the as_csv
method in place, put the following code in a file in config/initializers
inside your app (name it csv_renderer.rb
, for example):
require 'csv' # adds a .to_csv method to Array instances
class Array
alias old_to_csv to_csv #keep reference to original to_csv method
def to_csv(options = Hash.new)
# override only if first element actually has as_csv method
return old_to_csv(options) unless self.first.respond_to? :as_csv
# use keys from first row as header columns
out = first.as_csv.keys.to_csv(options)
self.each { |r| out << r.as_csv.values.to_csv(options) }
out
end
end
ActionController::Renderers.add :csv do |csv, options|
csv = csv.respond_to?(:to_csv) ? csv.to_csv() : csv
self.content_type ||= Mime::CSV
self.response_body = csv
end
And finally, add CSV support to your controller code:
class ModelnamesController < ApplicationController
respond_to :html, :json, :csv
def index
@modelnames = Modelname.all
respond_with(@modelnames)
end
# ...
end
The initializer code is largely based on the :json and :xml behaviour from the Rails source code (see link above).
Currently, the options
hash passed to the block doesn't get passed to the to_csv
call, as CSV is quite picky on which options it allows to be sent. Rails adds some default options by itself (like :template and some others), which gives you an error when passing them to to_csv
. You can change the default CSV rendering behaviour by adding your own preferred CSV options to the initializer, of course.
Hope this helps!