CSV Download of any given model

I found sample code from the archives of this list to make CSV
download of a model. It works fine. I am wondering if it is possible
to generalize the code posted by François on his blog to handle any
given model.

class ReportController < ApplicationController
def report
   @models = Model.find(:all, :conditions => ['...'])
   report = StringIO.new
   CSV::Writer.generate(report, ',') do |csv|
     csv << %w(Title Total)
     @models.each do |model|
       csv << [model.title, model.total]

     :type => 'text/csv; charset=iso-8859-1; header=present',
     :filename => 'report.csv')


I'm curious on this as well... my code is a little different then that
of the blog post referenced:

class SignaturesController < ApplicationController
  def export
    require 'csv'
    content_type = if request.user_agent =~ /windows/i

    CSV::Writer.generate(output = "") do |csv|
      csv << Signature.column_names
      Signature.find(:all).each do |signature|
        csv << [signature.id, signature.firstname, signature.lastname]
    send_data(output, :type => content_type, :filename =>

I'm already using Model.column_names to get the header of the CSV
file. Is there a way to loop through Model.content_columns and grab
the name variable to include within the block rather then specifying
the column individually?

Thoughts from anyone? Thanks...


I think you'll find this simpler and more flexible. First, you'll
need to download the FasterCSV gem ('gem install fastercsv'). Then
you can use:

class FasterCSVExport
  require 'fastercsv'

  def create_report( field_names = [], data_rows = [] )
    FasterCSV.generate do |csv|
      csv << field_names.map {|fn| fn.humanize.titleize }
      data_rows.each {|row| csv << row }

I've given it to you as a stand-alone class but the create_report
method can also be added to each model directly or as part of a module
that gets mixed in (which is how I do it). Then in your controller
you can just have:

field_names = ['id', 'firstname', 'lastname']
table_rows = YourModel.find(:all).collect {|item| [item.id,
item.firstname, item.lastname]
export_data = FasterCSVExport.create_report(field_names, table_rows)
send_data(export_data, :type => "text/csv; charset=utf-8;
  :disposition => 'attachment', :filename => "exported_data.csv")

Even easier, you can pass your field_names to the "select" option when
performing the find to quickly have both the table header and the
table rows contain the same attributes. Something like:

field_names = ['id', 'firstname', 'lastname']
table_rows = YourModel.find(:all, :select => field_names)
export_data = FasterCSVExport.create_report(field_names, table_rows)
send_data(export_data, :type => "text/csv; charset=utf-8;
  :disposition => 'attachment', :filename => "exported_data.csv")

Or if you opt to put the 'create_report' method into your model
directly, you can push the find to the model too and let it handle
both steps at once.

export_data = YourModel.create_report(['id', 'firstname', 'lastname'])
send_data(export_data, :type => "text/csv; charset=utf-8;
  :disposition => 'attachment', :filename => "exported_data.csv")

From there you can start to add bells and whistles. Tim, something

  field_names = YourModel.column_names if field_names.blank?
might be what you want to grab all the column names.

(I'm typing this from scratch without testing it so it's quite
possible I've typo'd something...)

Kevin Skoglund


Thank you Kevin - that makes sense, I'll check it out when I get back
to the office tomorrow.