Export Records to CSV Files with Rails
Victor Hazbun

Victor Hazbun @victorhazbun

About: Founder at @BonsaiLabs

Location:
Colombia
Joined:
Jun 22, 2019

Export Records to CSV Files with Rails

Publish Date: Jun 22 '19
23 11

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
Enter fullscreen mode Exit fullscreen mode

Routes

# routes.rb

...

resources :users, only: :index

...
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Comments 11 total

  • Thomas R. Koll
    Thomas R. KollJun 28, 2019

    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
    
  • Nuno
    NunoSep 9, 2019

    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.

    • Victor Hazbun
      Victor HazbunNov 21, 2019

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

  • Von Christian
    Von ChristianNov 21, 2019

    How to run it in background job(Sidekiq) ?

    • Victor Hazbun
      Victor HazbunNov 21, 2019

      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.

      • Von Christian
        Von ChristianNov 21, 2019

        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?

        • Victor Hazbun
          Victor HazbunNov 21, 2019

          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
  • haroldus-
    haroldus-Nov 28, 2019

    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
    
    Enter fullscreen mode Exit fullscreen mode

    Then

    # models/user.rb
    class User
      include GenerateCSV
    end
    
    Enter fullscreen mode Exit fullscreen mode

    or

    # models/whatever.rb
    class Whatever
     include GenerateCSV
    end
    
    Enter fullscreen mode Exit fullscreen mode
  • Victor Hazbun
    Victor HazbunFeb 28, 2020

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

    • countalucard2022
      countalucard2022May 31, 2020

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

Add comment