loading...
Cover image for Export Records to CSV Files with Rails

Export Records to CSV Files with Rails

victorhazbun profile image Victor Hazbun Updated on ・1 min read

Learn how to export records into CSV files using Ruby on Rails. Here are the steps.

Steps:

  1. Add a controller and make sure you handle the csv request.
  2. Add a route to point to your controller
  3. Add a model with the class method to_csv

Lets code it!

Controller

# users_controller.rb

class UsersController < ApplicationController
  def index
    @users = User.all

    respond_to do |format|
      format.html
      format.csv { send_data @users.to_csv, filename: "users-#{Date.today}.csv" }
    end
  end

Routes

# routes.rb

...

resources :users, only: :index

...

Model

# user.rb

class User < ActiveRecord::Base
  def self.to_csv
    attributes = %w{id email name}

    CSV.generate(headers: true) do |csv|
      csv << attributes

      all.find_each do |user|
        csv << attributes.map{ |attr| user.send(attr) }
      end
    end
  end

  def name
    "#{first_name} #{last_name}"
  end
end

Core custom methods:

to_csv will map out the "id, email, name" values of your model collection and then generate a CSV string.


Rails core methods for this feature:

#send_data Sends the given binary data to the browser. This method is similar to render :text => data, but also allows you to specify whether the browser should display the response as a file attachment (i.e. in a download dialog) or as inline data. You may also set the content type, the apparent file name, and other things. Source

Posted on by:

Discussion

markdown guide
 

Nitpicking if I may, a missing last or first name won't be noticed in HTML but it will be noticed in CSV when a human reads it directly or imported in Excel/LibreCalc.

def name
  [first_name, last_name].compact.join(' ')
end
 

How about doing this as a concern instead?

# models/concerns/generate_csv.rb
module GenerateCSV
  extend ActiveSupport::Concern

  class_methods do
    def generate_csv
      CSV.generate(headers: true) do |csv|
        csv << self.attribute_names

        all.each do |record|
          csv << record.attributes.values
        end
      end
    end
  end
end

Then

# models/user.rb
class User
  include GenerateCSV
end

or

# models/whatever.rb
class Whatever
 include GenerateCSV
end
 

Great for DRY approach, thanks for this :)

 

Nice, but there's a mistake in the controller/ model. The code appears to work because @users=User.all which matches the set of users computed within the model class

The correct call would be User.to_csv(@users). The User model function has to be modified to take the new parameter into account.

 

nope, it's right the way it is. Since @users is an ActiveRecord::Relation so you can say @users.to_csv

 

How to run it in background job(Sidekiq) ?

 

It depends, are you going to send an email on a background job? or are you going to upload the file on S3? please give some context.

 

from my experience, when large amounts of data is involved, the server times out. Im thinking if it is possible to move the CSV export and show a download link to the user?

Ok, then you will need to do the following:

  1. On a BG job generate the CSV file
  2. Upload the file to AWS S3
  3. Save the URL in the DB
  4. Expose the URL in the front-end
 

UPDATE: I was using #each, I changed it to#find_each which saves memory. See api.rubyonrails.org/classes/Active...

 

But this will not be a report friendly, find_each forces the sequence to be order by id desc :( thanks to the postoverall!