Last active
September 4, 2018 19:28
-
-
Save d1rtyvans/95507f04fb6dc8b2bf002f00bf6f8876 to your computer and use it in GitHub Desktop.
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
# 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 |
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
# 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