Returning CSV data to the browser
Over on the Rails mailing list, Pete asks:
One of my apps has to export data for the backend system to process it. What’s the best way to create a CSV file in Rails and then return it as a file to the client?
I have used two tricks in the past to export the data.
Exporting using CSV::Writer
This is simple. Use CSV::Writer and ActionController’s send_data method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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 |
By default, send_data will make the Content-Disposition header equal to attachment. This will ask the browser to download the file, instead of displaying it in the browser window.
Exporting using a regular view
The second method is even simpler:
1 2 3 4 5 6 7 |
class ReportController < ApplicationController def report @models = Model.find(:all, :conditions => ['...']) response.headers['Content-Type'] = 'text/csv; charset=iso-8859-1; header=present' response.headers['Content-Disposition'] = 'attachment; filename=report.csv' end end |
1 2 3 4 |
Title,Value <% @models.each do |model| -%> "<%= model.title.gsub('"', '""') %>",<%= model.value %> <% end -%> |
Notice how I escape quotes in the view ? This is important, or else your parsing will be broken when you read the file back in. Of course, if you use CSV::Writer, you won’t have to muck with that – it will all be taken care of for you.
UPDATE 2006-03-30: Changed Content-Type from text/comma-separated-values to text/csv, and added the header optional parameter, per RFC 4180
UPDATE 2007-04-26: Just noticed that the report method above would render with a layout if one were defined in ApplicationController. Simply call #render and tell it to use no layout: render :action => :report, :layout => false
October 12th, 2007 at 10:25 PM
Nice! Thanks for sharing this.
October 12th, 2007 at 10:25 PM
Nice write-up. One suggested change, though, per RFC-4180: the content type for comma-separated values is “text/csv”, not “text/comma-separated-values”.
October 12th, 2007 at 10:25 PM
Thanks! Excellent example, my client loves this feature and was astounded when I implemented it on the fly.
February 13th, 2008 at 08:10 AM
“Content-Disposition” – ah, now that’s what I’ve been looking for. Ta!