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
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
ExportServices::ExportTransactionService.new({ user: User.last, from_date: '2022-01-01', to_date: '2022-09-01'}).call
Feel free to ask more questions in the comment section.
Happy Coding!
Top comments (0)