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]      end    end

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


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                      'application/vnd.ms-excel'                    else                      'text/csv'                    end

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

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 }     end   end end

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; header=present",   :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; header=present",   :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; header=present",   :disposition => 'attachment', :filename => "exported_data.csv")

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

like:   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...)

HTH, Kevin Skoglund


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