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.
app/controllers/report_controller.rb
1 class ReportController < ApplicationController 2 def report 3 @models = Model.find(:all, :conditions => ['...']) 4 report = StringIO.new 5 CSV::Writer.generate(report, ',') do |csv| 6 csv << %w(Title Total) 7 @models.each do |model| 8 csv << [model.title, model.total] 9 end 10 end 11 12 report.rewind 13 send_data(report.read, 14 :type => 'text/csv; charset=iso-8859-1; header=present', 15 :filename => 'report.csv') 16 end 17 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:
app/controllers/report_controller.rb
1 class ReportController < ApplicationController 2 def report 3 @models = Model.find(:all, :conditions => ['...']) 4 response.headers['Content-Type'] = 'text/csv; charset=iso-8859-1; header=present' 5 response.headers['Content-Disposition'] = 'attachment; filename=report.csv' 6 end 7 end
app/views/report/report.rhtml
1 Title,Value 2 <% @models.each do |model| -%> 3 "<%= model.title.gsub('"', '""') %>",<%= model.value %> 4 <% 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