-
-
Save joeljunstrom/3b70337c4f38a2a7de104ef2d5d67ce9 to your computer and use it in GitHub Desktop.
Exporting customers in the background example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
class Exporters::Customers | |
attr_reader :file_export | |
def initialize(file_export) | |
@file_export = file_export | |
end | |
def call | |
Admin::CustomersCsv.new(query).generate | |
end | |
def filename | |
"customers-#{Time.current.to_s(:filename)}.csv" | |
end | |
private | |
def query | |
Customer.all | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
require "csv" | |
class Admin::CustomersCsv | |
HEADERS = | |
["id", "First name", "Last name", "Email", "Street", "Postal code", "Locality", "Country"].freeze | |
def initialize(scope) | |
@scope = scope | |
end | |
def generate(destination = Tempfile.new) | |
CSV.open(destination, "w", headers: HEADERS, write_headers: true) do |csv| | |
customer_query.find_each do |row| | |
csv << [ | |
row.id, | |
row.first_name, | |
row.last_name, | |
row.email_address, | |
row.street_name, | |
row.postal_code, | |
row.locality, | |
CountryName.new(row.country_code).value | |
] | |
end | |
end | |
destination.rewind | |
destination | |
end | |
private | |
def customer_query | |
identity_table = Identity.arel_table | |
address_table = Address.arel_table | |
email_table = Email.arel_table | |
scope | |
.left_outer_joins(:identity, :address) | |
.joins(user: :email) | |
.select( | |
:id, | |
:user_id, | |
:identity_id, | |
:address_id, | |
email_table[:address].as("email_address"), | |
identity_table[:first_name], | |
identity_table[:last_name], | |
address_table[:street_name], | |
address_table[:postal_code], | |
address_table[:locality], | |
address_table[:country].as("country_code") | |
) | |
end | |
attr_reader :scope | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
class Exporters | |
# Receives a file export instance and looks up which exporter to use | |
# and calls it | |
class << self | |
def call(file_export) | |
for_file_export(file_export).call | |
end | |
def for_file_export(file_export) | |
klass = | |
case file_export.data_type | |
when "customers" | |
Exporters::Customers | |
when "product_subscriptions" | |
Exporters::ProductSubscriptions | |
else | |
raise ArgumentError, "No exporter class found for #{file_export.data_type}" | |
end | |
new(exporter: klass.new(file_export)) | |
end | |
end | |
attr_reader :exporter | |
def initialize(exporter:) | |
@exporter = exporter | |
end | |
# Run the export and attach the resulting csv on the file export | |
def call | |
file = exporter.call | |
FileExport.transaction do | |
exporter | |
.file_export | |
.file | |
.attach(io: File.open(file.path), filename: exporter.filename) | |
exporter.file_export.update_column(:generated_at, Time.current) | |
end | |
exporter | |
ensure | |
file&.close | |
file&.unlink | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
class FileExport < ApplicationRecord | |
AVAILABLE_FOR = 1.day | |
has_one_attached :file | |
belongs_to :product, optional: true | |
class << self | |
def prepare! | |
FileExport.transaction do | |
FileExport.insert( | |
{data_type: "customers"}, | |
returning: false, | |
unique_by: :index_file_exports_on_data_type | |
) | |
subscription_exports_attrs = | |
Product.pluck(:id).map { |id| | |
{data_type: "product_subscriptions", product_id: id} | |
} | |
if subscription_exports_attrs.any? | |
FileExport.insert_all( | |
subscription_exports_attrs, | |
returning: false, | |
unique_by: :index_file_exports_on_data_type_and_product_id | |
) | |
end | |
end | |
end | |
end | |
def available_until | |
(generated_at + AVAILABLE_FOR) if generated_at | |
end | |
def generating! | |
update_columns(generating: true, generated_at: nil) | |
end | |
def finished! | |
update_columns(generating: false) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
class FileExportJob < ApplicationJob | |
sidekiq_options retry: 3 | |
def perform(file_export_id) | |
file_export = FileExport.find_by(id: file_export_id) | |
return unless file_export | |
Exporters.call(file_export) | |
file_export.finished! | |
rescue | |
file_export.finished! | |
raise | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Admin::FileExportsController < Admin::AdminController | |
menu :exports | |
def index | |
FileExport.prepare! | |
exports = | |
FileExport | |
.includes(:product, :file_attachment, :file_blob) | |
.order(id: :asc) | |
.load | |
render locals: {exports: exports} | |
end | |
def generate | |
file_export = FileExport.find(params[:id]) | |
file_export.generating! | |
FileExportJob.perform_later(file_export.id) | |
redirect_to admin_file_exports_path | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
class Exporters::ProductSubscriptions | |
delegate :product, to: :file_export | |
attr_reader :file_export | |
def initialize(file_export) | |
@file_export = file_export | |
end | |
def call | |
Admin::CustomersCsv.new(query).generate | |
end | |
def filename | |
"#{product.name.parameterize}-#{Time.current.to_s(:filename)}.csv" | |
end | |
private | |
def query | |
Customer | |
.joins(subscriptions: [:products]) | |
.where( | |
subscriptions: {starts_at: (..Time.current), ends_at: (Time.current..)}, | |
package_products: {product_id: product.id} | |
) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment