DEV Community

Cover image for Export User Data As CSV and Send An Email with Ruby on Rails
Sulman Baig
Sulman Baig

Posted on • Originally published at sulmanweb.com

Export User Data As CSV and Send An Email with Ruby on Rails

Sometimes we need to export user data we have in our app to comply with GDPR. So here I will explain how to export user data as a CSV file and attach it to an email or just upload it to ActiveStorage and S3.

In this example code, I have an app where users save their financial transactions with bank accounts’ names, currencies, and transaction dates.

We will create a temp file in the rails temp directory and then send it to SendGrid for email or storage. We will add the data to a temporary CSV file then call export_to_storage to save it to ActiveStorage to call send_email to send the user email with the attached CSV file.

In this article, I have already explained how to send emails with attachments using SendGrid.

app/services/export_services/export_transaction_service.rb

# frozen_string_literal: true

# app/services/export_services/export_transaction_service.rb
# Example:
#   ExportServices::ExportTransactionService.new({ user: User.last}).call
#   ExportServices::ExportTransactionService.new({ user: User.last, from_date: '2022-01-01', to_date: '2022-09-01'}).call
module ExportServices
  # ExportTransactionService
  class ExportTransactionService
    # initialize the service with user, from_date and to_date and setting transactions accordingly
    def initialize(params) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
      @user = params[:user]
      @from_date = params[:from_date] ? params[:from_date].to_date : 10.years.ago # -Infinity
      @to_date = params[:to_date] ? params[:to_date].to_date : Time.zone.now
      @transactions = @user.transactions.where(transaction_date: @from_date..@to_date)
                           .joins(:account, :category).kept.order(transaction_date: :desc)
    end

    # service work when call method is called
    def call
      return if @transactions.length.zero?

      generate_csv_file
      # export_to_storage
      send_email
    end

    # This method creates a temp CSV file and fills data based on initialized transactions
    def generate_csv_file # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
      @tempfile = Tempfile.new(["export_#{Time.zone.now.strftime('%Y%m%d%H%M%S')}_#{@user.id}", '.csv']).tap do |file|
        CSV.open(file, 'wb') do |csv|
          # CSV Header Row
          csv << %w[Date Description Type Amount Currency Category Account]

          # CSV Rows, each row representing a transaction
          @transactions.find_each do |transaction|
            csv << [
              transaction.transaction_date.to_date.to_s,
              transaction.description,
              transaction.transaction_type == 'income' ? 'Income' : 'Expense',
              transaction.value.to_f.round(2),
              transaction.account.currency.code,
              transaction.category.name,
              transaction.account.name,
            ]
          end
        end
      end
    end

    # Thios method adds the tempfile to ActiveStorage and hence to S3 service if attached
    # I have attached s3 to production environment
    # returns url of the uploaded CSV File
    def export_to_storage # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
      # Document Model `has_attached_file :doc` and `user has_many :documents` and `document belongs_to :user`
      document = Document.new(user: @user)
      document.doc.attach(
        io: @tempfile,
        filename: @tempfile.path.split('/').last,
        content_type: 'application/csv'
      )
      document.save!
      @file_url = if Rails.env.production?
                    # returns s3 public url
                    Rails.application.routes.url_helpers.rails_public_blob_url(@document.doc)
                  else
                    # return local url
                    Rails.application.routes.url_helpers.rails_blob_url(@document.doc)
                  end
    end

    # This method sends email with temp file attached
    # https://gist.github.com/sulmanweb/eab697301cf8209572c5a95af6543b3a
    # https://sulmanweb.com/send-emails-using-sendgrid-sdk-for-ruby-on-rails-using-dynamic-sendgrid-email-templates/
    def send_email # rubocop:disable Metrics/MethodLength
      EmailJob.new.perform(
        @user.id,
        { name: @user.name },
        ENV.fetch('EXPORT_TEMPLATE', nil),
        [
          {
            file: @tempfile.path,
            type: 'application/csv',
            name: @tempfile.path.split('/').last,
            content_id: 'export_file'
          }
        ]
      )
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

GitHub Gist

In Line 11, we initialize the service with params, required is the user, and optional are date ranges. After initialization, we will get the provided user’s transactions.

The call method on Line 20 checks whether the user has at least one transaction, then it calls generate_csv_file which creates a temp CSV file with the user’s transactions and then uses send_email to send that email with the attached file.

The method export_to_storage on line 54 is optional if we want to upload to s3. In that case, this method will return the URL of s3 or the local storage of the file.

Examples to call this service in your code be like:

ExportServices::ExportTransactionService.new({ user: User.last}).call
Enter fullscreen mode Exit fullscreen mode
ExportServices::ExportTransactionService.new({ user: User.last, from_date: '2022-01-01', to_date: '2022-09-01'}).call
Enter fullscreen mode Exit fullscreen mode

Feel free to ask more questions in the comment section.


Happy Coding!

Top comments (0)