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
3models</span> = <span class="co">Model</span>.find(<span class="sy">:all</span>, <span class="sy">:conditions</span> => [<span class="s"><span class="dl">'</span><span class="k">...</span><span class="dl">'</span></span>]) <span class="no"> 4</span> report = <span class="co">StringIO</span>.new <span class="no"> <strong>5</strong></span> <span class="co">CSV</span>::<span class="co">Writer</span>.generate(report, <span class="s"><span class="dl">'</span><span class="k">,</span><span class="dl">'</span></span>) <span class="r">do</span> |csv| <span class="no"> 6</span> csv << <span class="s"><span class="dl">%w(</span><span class="k">Title Total</span><span class="dl">)</span></span> <span class="no"> 7</span> <span class="iv">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