in rails, how to return records as a csv file
-
01-07-2019 - |
Question
I have a simple database table called "Entries":
class CreateEntries < ActiveRecord::Migration
def self.up
create_table :entries do |t|
t.string :firstName
t.string :lastName
#etc.
t.timestamps
end
end
def self.down
drop_table :entries
end
end
How do I write a handler that will return the contents of the Entries table as a CSV file (ideally in a way that it will automatically open in Excel)?
class EntriesController < ApplicationController
def getcsv
@entries = Entry.find( :all )
# ??? NOW WHAT ????
end
end
Solution
There is a plugin called FasterCSV that handles this wonderfully.
OTHER TIPS
FasterCSV is definitely the way to go, but if you want to serve it directly from your Rails app, you'll want to set up some response headers, too.
I keep a method around to set up the filename and necessary headers:
def render_csv(filename = nil)
filename ||= params[:action]
filename += '.csv'
if request.env['HTTP_USER_AGENT'] =~ /msie/i
headers['Pragma'] = 'public'
headers["Content-type"] = "text/plain"
headers['Cache-Control'] = 'no-cache, must-revalidate, post-check=0, pre-check=0'
headers['Content-Disposition'] = "attachment; filename=\"#{filename}\""
headers['Expires'] = "0"
else
headers["Content-Type"] ||= 'text/csv'
headers["Content-Disposition"] = "attachment; filename=\"#{filename}\""
end
render :layout => false
end
Using that makes it easy to have something like this in my controller:
respond_to do |wants|
wants.csv do
render_csv("users-#{Time.now.strftime("%Y%m%d")}")
end
end
And have a view that looks like this: (generate_csv
is from FasterCSV)
UserID,Email,Password,ActivationURL,Messages
<%= generate_csv do |csv|
@users.each do |user|
csv << [ user[:id], user[:email], user[:password], user[:url], user[:message] ]
end
end %>
I accepted (and voted up!) @Brian's answer, for first pointing me to FasterCSV. Then when I googled to find the gem, I also found a fairly complete example at this wiki page. Putting them together, I settled on the following code.
By the way, the command to install the gem is: sudo gem install fastercsv (all lower case)
require 'fastercsv'
class EntriesController < ApplicationController
def getcsv
entries = Entry.find(:all)
csv_string = FasterCSV.generate do |csv|
csv << ["first","last"]
entries.each do |e|
csv << [e.firstName,e.lastName]
end
end
send_data csv_string, :type => "text/plain",
:filename=>"entries.csv",
:disposition => 'attachment'
end
end
Another way to do this without using FasterCSV:
Require ruby's csv library in an initializer file like config/initializers/dependencies.rb
require "csv"
As some background the following code is based off of Ryan Bate's Advanced Search Form that creates a search resource. In my case the show method of the search resource will return the results of a previously saved search. It also responds to csv, and uses a view template to format the desired output.
def show
@advertiser_search = AdvertiserSearch.find(params[:id])
@advertisers = @advertiser_search.search(params[:page])
respond_to do |format|
format.html # show.html.erb
format.csv # show.csv.erb
end
end
The show.csv.erb file looks like the following:
<%- headers = ["Id", "Name", "Account Number", "Publisher", "Product Name", "Status"] -%>
<%= CSV.generate_line headers %>
<%- @advertiser_search.advertisers.each do |advertiser| -%>
<%- advertiser.subscriptions.each do |subscription| -%>
<%- row = [ advertiser.id,
advertiser.name,
advertiser.external_id,
advertiser.publisher.name,
publisher_product_name(subscription),
subscription.state ] -%>
<%= CSV.generate_line row %>
<%- end -%>
<%- end -%>
On the html version of the report page I have a link to export the report that the user is viewing. The following is the link_to that returns the csv version of the report:
<%= link_to "Export Report", formatted_advertiser_search_path(@advertiser_search, :csv) %>
Take a look into the FasterCSV gem.
If all you need is excel support, you might also look into generating a xls directly. (See Spreadsheet::Excel)
gem install fastercsv
gem install spreadsheet-excel
I find these options good for opening the csv file in Windows Excel:
FasterCSV.generate(:col_sep => ";", :row_sep => "\r\n") { |csv| ... }
As for the ActiveRecord part, something like this would do:
CSV_FIELDS = %w[ title created_at etc ]
FasterCSV.generate do |csv|
Entry.all.map { |r| CSV_FIELDS.map { |m| r.send m } }.each { |row| csv << row }
end
You need to set the Content-Type header in your response, then send the data. Content_Type: application/vnd.ms-excel should do the trick.
You may also want to set the Content-Disposition header so that it looks like an Excel document, and the browser picks a reasonable default file name; that's something like Content-Disposition: attachment; filename="#{suggested_name}.xls"
I suggest using the fastercsv ruby gem to generate your CSV, but there's also a builtin csv. The fastercsv sample code (from the gem's documentation) looks like this:
csv_string = FasterCSV.generate do |csv|
csv << ["row", "of", "CSV", "data"]
csv << ["another", "row"]
# ...
end
The following approached worked well for my case and causes the browser to open the appropriate application for the CSV type after downloading.
def index
respond_to do |format|
format.csv { return index_csv }
end
end
def index_csv
send_data(
method_that_returns_csv_data(...),
:type => 'text/csv',
:filename => 'export.csv',
:disposition => 'attachment'
)
end
try a nice gem to generate CSV from Rails https://github.com/crafterm/comma
Take a look at the CSV Shaper gem.
https://github.com/paulspringett/csv_shaper
It has a nice DSL and works really well with Rails models. It also handles the response headers and allows filename customisation.
If you're simply wanting to get the csv database yourself from the console you can do so in a few lines
tags = [Model.column_names]
rows = tags + Model.all.map(&:attributes).map(&:to_a).map { |m| m.inject([]) { |data, pair| data << pair.last } }
File.open("ss.csv", "w") {|f| f.write(rows.inject([]) { |csv, row| csv << CSV.generate_line(row) }.join(""))}