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!

28