Skip to content

Instantly share code, notes, and snippets.

@d1rtyvans
Last active September 4, 2018 19:28
Show Gist options
  • Save d1rtyvans/95507f04fb6dc8b2bf002f00bf6f8876 to your computer and use it in GitHub Desktop.
Save d1rtyvans/95507f04fb6dc8b2bf002f00bf6f8876 to your computer and use it in GitHub Desktop.
# app/services/results/create_export.rb
# Service object that exports assignments, transactions, and views data (for Whatify's analysis) to csv,
# uploads to Dropbox, and notifies our CEO who runs his analysis using Stata.
# Full test coverage and easy extendability
module Results
class CreateExport
def initialize
upload_spreadsheets
send_email_reminder
create_export
end
private
def upload_spreadsheets
spreadsheets.each { |sheet| Upload.new(sheet.filepath) }
end
def send_email_reminder
ResultsMailer.results_exported.deliver_now
end
def create_export
Export.create
end
def spreadsheets
[
Csv::Views.new,
Csv::Transactions.new,
Csv::Assignments.new,
]
end
end
end
# app/services/results/upload.rb
# Uploads file to dropbox and removes tempfile
class Results::Upload
include HTTParty
base_uri 'https://content.dropboxapi.com/2'
attr_reader :path
def initialize(path)
@path = path
upload_to_dropbox
delete_tempfile
end
def upload_to_dropbox
self.class.post("/files/upload", headers: headers, body: read_file)
end
def delete_tempfile
File.delete(path)
end
private
def headers
{
"Authorization" => "Bearer #{ENV['DROPBOX_KEY']}",
"Content-Type" => "application/octet-stream",
"Dropbox-API-Arg" => "{\"path\":\"/Experimento/Shopify/export/#{path}\"}"
}
end
def read_file
File.read(path)
end
end
# app/services/results/csv/assignments.rb
# Queries assignments data (corresponds to price randomizations on Shopify)
module Results::Csv
class Assignments < Base
def filename
'assignments'
end
def sql_query
<<-SQL
select
exp.shop_id,
exp.external_product_id,
ass.start,
lead(ass.start - interval '1 second', 1) over (order by ass.start) as date_end,
(select var.value
from variables var
where var.experiment_id = ass.experiment_id and (
ass.assignment_index = 0 and var.name = 'price1' or
ass.assignment_index = 1 and var.name = 'price2')
) active_price
from assignments ass
inner join experiments exp on ass.experiment_id = exp.id
where ass.created_at > '#{last_export}';
SQL
end
end
end
# app/services/results/csv/transactions.rb
# Queries transactions data
module Results::Csv
class Transactions < Base
include UseStashBase
def filename
'transactions'
end
def sql_query
<<-SQL
SELECT
date,
shop_id,
external_product_id,
quantity,
price,
discount,
order_total
FROM orders
WHERE created_at > '#{last_export}';
SQL
end
end
end
# app/services/results/csv/base.rb
# Base serves as template for csv spreadsheets,
# It houses all common functionality between csv spreadsheets,
# creating the file, executing the datbase query, etc.
module Results::Csv
class Base
def initialize
CSV.open(filepath, 'w') do |csv|
csv << records.fields
records.values.each { |row| csv << row }
end
end
def records
@_records ||= database.connection.execute(sql_query)
end
def filepath
filename + timestamp + '.csv'
end
def filename
fail 'Need to define `filename` for spreadsheet'
end
def sql_query
fail 'Need to define `sql_query` for spreadsheet'
end
private
def database
ActiveRecord::Base
end
# We are using two different databases, otherwise this would be calculated in the `Csv` query
def last_export
@_last_export ||= Export.last.created_at
end
def timestamp
Date.today.strftime('%m%d%y')
end
end
end
# app/services/results/csv/use_stash_base.rb
# include this to query stash database instead of default
module Results::Csv
module UseStashBase
def database
StashBase
end
end
end
# spec/services/results/create_export_spec.rb
require 'rails_helper'
require_dependency 'services/results/create_export'
describe Results::CreateExport do
describe 'results export' do
before do
Results::Upload.stubs(:new)
ActionMailer::Base.deliveries = []
end
after :all do
Dir.glob('*.csv').each { |f| File.delete(f) }
end
subject do
Results::CreateExport.new
end
it 'adds row to export table' do
subject
expect(Export.count).to eq(1)
end
it 'uploads spreadsheets' do
Results::Upload.expects(:new).times(3)
subject
end
it 'sends email reminder' do
subject
expect(ActionMailer::Base.deliveries.count).to eq(1)
end
end
end
# spec/services/results/upload_spec.rb
require 'rails_helper'
require_dependency 'services/results/upload'
describe Results::Upload do
describe '#initialize' do
PATH = 'spec/fixtures/exported_spreadsheet.csv'
subject do
Results::Upload.new(PATH)
end
before do
File.open(PATH, 'w')
stub_dropbox_request
end
# Make sure request to dropbox api is as expected
it 'uploads to Experimento/Shopify/export' do
subject
end
it 'deletes tempfile once upload is complete' do
subject
expect(File.file?("exported_spreadsheet.csv")).to eq(false)
end
end
end
# spec/services/results/csv/assignments_spec.rb
require 'rails_helper'
require_dependency 'services/results/csv/assignments'
describe Results::Csv::Assignments do
subject do
Results::Csv::Assignments.new.records
end
context 'scope' do
before do
create(:assignment, :with_experiment, created_at: 10.days.ago)
create(:export)
create(:assignment, experiment: create(:experiment, external_product_id: 420))
end
it 'only queries assignments created after last export' do
records = subject
expect(records.count).to eq(1)
expect(records[0]['external_product_id']).to eq('420')
end
end
context 'formatting' do
let(:assignment) { create(:assignment, :with_experiment) }
before do
assignment
create :assignment, :with_experiment
end
it 'properly formats date_end of assignment' do
date_end = subject[0]['date_end'].to_time.strftime('%Y-%m-%d %H:%M:%S')
expected = (assignment.start - 1.second).strftime('%Y-%m-%d %H:%M:%S')
expect(date_end).to eq expected
end
end
end
# spec/services/results/csv/transaction_spec.rb
require 'rails_helper'
require_dependency 'services/results/csv/transactions'
describe Results::Csv::Transactions do
context 'scope' do
before do
create(:order, created_at: 10.seconds.ago)
create(:export)
create(:order)
create(:order)
end
subject do
Results::Csv::Transactions.new.records
end
it 'only queries transactions created after last export' do
records = subject
expect(records.count).to eq(2)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment