How to Export to CSV with Ruby on Rails
So, you need to export a CSV.
As with everything in Ruby on Rails, it's really easy to do this.
I will walk through how I built this feature to export sales leads on another Rails app of mine. But this same approach can be applied to any data you want to export as a CSV file.
We are going to build an endpoint that a user can navigate to, that will trigger a download of the CSV file.
The idea is, they land on a route like this https://example.com/export/leads.csv
and we start a download instead of rendering a normal page.
Create the Route
First we need to create a route that we point the user to.
Add something like this to your config/routes.rb
file:
get 'leads/export', to: 'leads#export'
Change the name of your controller to whatever you want. Same with the method name. I went with export
because that makes it clear what this method is responsible for.
Create the Controller
Rails will expect the leads controller to be defined at app/controllers/leads_controller.rb
, let's create that.
Initially, the contents of this controller should look something like this.
require 'csv'
class LeadsController < ApplicationController
def export
@leads = Leads.where(organization_id: current_user.organization_id)
respond_to do |format|
format.csv do
response.headers['Content-Type'] = 'text/csv'
response.headers['Content-Disposition'] = "attachment; filename=leads.csv"
end
end
end
end
We need our export
method to do a few things here.
- pull data from the database
- convert our data into a CSV file
- format the response so we download the CSV
First let's pull the data. We'll need to build a query and save the results to an instance variable.
@leads = Leads.where(organization_id: current_user.organization_id)
Next we have to convert the result of this query into the CSV format.
At the top of our controller let's import the built-in Rails csv
library:
require 'csv'
And now we can format our controller to respond when CSV is requested. Rails uses the respond_to
method to allow controller endpoints to respond to multiple formats. You will see a lot of endpoints that respond with HTML, JSON, and even XML.
Here's what those formats would normally look like.
respond_to do |format|
format.html
format.json { render json: @leads.to_json }
format.xml { render xml: @leads.to_xml }
end
We'll need to slightly modify this to return CSV data.
Our version will instead respond to format.csv
:
respond_to do |format|
format.csv do
response.headers['Content-Type'] = 'text/csv'
response.headers['Content-Disposition'] = "attachment; filename=leads.csv"
render template: "path/to/index.csv.erb"
end
end
This means when someone makes a request to our URL, which was https://example.com/export/leads.csv
, the tailing leads.csv
extension tells our server to respond with the csv format.
Also, these Content-Type headers tell the browser that we are returning a CSV file to download.
Building the CSV File
Our backend is fully wired up to export CSV for the user.
Now we need to generate the actual CSV file. The controller will automatically render the template found at app/views/leads/export.csv.erb
.
Let's create that, and then here's what it will look like:
<%- headers = ['Email', 'Date'] -%>
<%= CSV.generate_line headers %>
<%- @leads.each do |lead| -%>
<%= CSV.generate_line([lead.data['email'], lead.created_at]) -%>
<%- end -%>
Formatting a CSV file is very straightforward in Rails. We just need to call CSV.generate_line
to add a row.
First we declare a variable called headers
, and store the headers of our CSV file.
<%- headers = ['Email', 'Date'] -%>
Next we generate the first row for these headers with this call:
<%= CSV.generate_line headers %>
And last, we loop through our query result and again generate a row for each lead:
<%- @leads.each do |lead| -%>
<%= CSV.generate_line([lead.data['email'], lead.created_at]) -%>
<%- end -%>
And that's it.
Now we have an endpoint that a user can access to download their data as a CSV. You can point users to this endpoint with a button and the download should begin automatically.
Something like this should do the trick!
<a href="/leads/export.csv">Export CSV</a>
Of course, replace the query I used and pull whatever records you want in your controller.
Pass CSV.generate_line()
as many arguments as you want columns, and don't forget to pass headers in the first line to match!
For example, if you wanted to output the ID, email, date, and name of records in your leads table:
<%- headers = ['ID', 'Email', 'Date', 'Name'] -%>
<%= CSV.generate_line headers %>
<%- @leads.each do |lead| -%>
<%= CSV.generate_line([lead.data['id'], lead.data['email'], lead.data['created_at'], lead.data['name']]) -%>
<%- end -%>
I wrote a post on parsing and working with CSVs in Ruby if you're interested in learning more about the topic.