Move ProxyOrderSyncer to OrderManagement engine

This commit is contained in:
Luis Ramos
2020-02-11 18:22:36 +00:00
parent fb1c825fbc
commit ae0ceb61a1
13 changed files with 587 additions and 577 deletions

View File

@@ -101,6 +101,7 @@ Layout/LineLength:
- app/services/subscriptions_count.rb
- app/services/variants_stock_levels.rb
- engines/web/app/helpers/web/cookies_policy_helper.rb
- engines/order_management/spec/services/order_management/subscriptions/proxy_order_syncer_spec.rb
- lib/discourse/single_sign_on.rb
- lib/open_food_network/available_payment_method_filter.rb
- lib/open_food_network/bulk_coop_report.rb
@@ -239,7 +240,6 @@ Layout/LineLength:
- spec/lib/open_food_network/packing_report_spec.rb
- spec/lib/open_food_network/permissions_spec.rb
- spec/lib/open_food_network/products_and_inventory_report_spec.rb
- spec/lib/open_food_network/proxy_order_syncer_spec.rb
- spec/lib/open_food_network/scope_variant_to_hub_spec.rb
- spec/lib/open_food_network/tag_rule_applicator_spec.rb
- spec/lib/open_food_network/users_and_enterprises_report_spec.rb
@@ -684,6 +684,7 @@ Metrics/ModuleLength:
- app/helpers/injection_helper.rb
- app/helpers/spree/admin/navigation_helper.rb
- app/helpers/spree/admin/base_helper.rb
- engines/order_management/spec/services/order_management/subscriptions/proxy_order_syncer_spec.rb
- engines/order_management/spec/services/order_management/subscriptions/payment_setup_spec.rb
- lib/open_food_network/column_preference_defaults.rb
- spec/controllers/admin/enterprises_controller_spec.rb
@@ -700,7 +701,6 @@ Metrics/ModuleLength:
- spec/lib/open_food_network/order_grouper_spec.rb
- spec/lib/open_food_network/permissions_spec.rb
- spec/lib/open_food_network/products_and_inventory_report_spec.rb
- spec/lib/open_food_network/proxy_order_syncer_spec.rb
- spec/lib/open_food_network/scope_variant_to_hub_spec.rb
- spec/lib/open_food_network/tag_rule_applicator_spec.rb
- spec/lib/open_food_network/users_and_enterprises_report_spec.rb

View File

@@ -882,6 +882,7 @@ Style/FrozenStringLiteralComment:
- 'engines/order_management/app/services/order_management/reports/enterprise_fee_summary/report_service.rb'
- 'engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb'
- 'engines/order_management/app/services/order_management/reports/enterprise_fee_summary/summarizer.rb'
- 'engines/order_management/app/services/order_management/subscriptions/proxy_order_syncer.rb'
- 'engines/order_management/app/services/order_management/subscriptions/subscription_summarizer.rb'
- 'engines/order_management/app/services/order_management/subscriptions/subscription_summary.rb'
- 'engines/order_management/app/services/reports.rb'
@@ -895,6 +896,7 @@ Style/FrozenStringLiteralComment:
- 'engines/order_management/lib/order_management/engine.rb'
- 'engines/order_management/lib/order_management/version.rb'
- 'engines/order_management/order_management.gemspec'
- 'engines/order_management/spec/performance/order_management/subscriptions/proxy_order_syncer_spec.rb'
- 'engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/authorizer_spec.rb'
- 'engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/parameters_spec.rb'
- 'engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/permissions_spec.rb'
@@ -902,6 +904,7 @@ Style/FrozenStringLiteralComment:
- 'engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/renderers/html_renderer_spec.rb'
- 'engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total_spec.rb'
- 'engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb'
- 'engines/order_management/spec/services/order_management/subscriptions/proxy_order_syncer_spec.rb'
- 'engines/order_management/spec/services/order_management/subscriptions/subscription_summizer_spec.rb'
- 'engines/order_management/spec/services/order_management/subscriptions/subscription_summary_spec.rb'
- 'engines/order_management/spec/spec_helper.rb'
@@ -954,7 +957,6 @@ Style/FrozenStringLiteralComment:
- 'lib/open_food_network/products_and_inventory_report.rb'
- 'lib/open_food_network/products_and_inventory_report_base.rb'
- 'lib/open_food_network/property_merge.rb'
- 'lib/open_food_network/proxy_order_syncer.rb'
- 'lib/open_food_network/rack_request_blocker.rb'
- 'lib/open_food_network/referer_parser.rb'
- 'lib/open_food_network/reports/bulk_coop_allocation_report.rb'
@@ -1212,7 +1214,6 @@ Style/FrozenStringLiteralComment:
- 'spec/lib/open_food_network/permissions_spec.rb'
- 'spec/lib/open_food_network/products_and_inventory_report_spec.rb'
- 'spec/lib/open_food_network/property_merge_spec.rb'
- 'spec/lib/open_food_network/proxy_order_syncer_spec.rb'
- 'spec/lib/open_food_network/referer_parser_spec.rb'
- 'spec/lib/open_food_network/reports/report_spec.rb'
- 'spec/lib/open_food_network/reports/row_spec.rb'
@@ -1306,7 +1307,6 @@ Style/FrozenStringLiteralComment:
- 'spec/models/variant_override_spec.rb'
- 'spec/performance/injection_helper_spec.rb'
- 'spec/performance/orders_controller_spec.rb'
- 'spec/performance/proxy_order_syncer_spec.rb'
- 'spec/performance/shop_controller_spec.rb'
- 'spec/requests/checkout/failed_checkout_spec.rb'
- 'spec/requests/checkout/paypal_spec.rb'

View File

@@ -1,5 +1,5 @@
require 'open_food_network/permissions'
require 'open_food_network/proxy_order_syncer'
require 'order_management/subscriptions/proxy_order_syncer'
module Admin
class SchedulesController < ResourceController
@@ -81,7 +81,7 @@ module Admin
return unless removed_ids.any? || new_ids.any?
subscriptions = Subscription.where(schedule_id: @schedule)
syncer = OpenFoodNetwork::ProxyOrderSyncer.new(subscriptions)
syncer = OrderManagement::Subscriptions::ProxyOrderSyncer.new(subscriptions)
syncer.sync!
end
end

View File

@@ -1,6 +1,6 @@
require 'open_food_network/permissions'
require 'open_food_network/proxy_order_syncer'
require 'open_food_network/order_cycle_form_applicator'
require 'order_management/subscriptions/proxy_order_syncer'
class OrderCycleForm
def initialize(order_cycle, params, user)
@@ -58,7 +58,7 @@ class OrderCycleForm
return unless schedule_ids?
return unless schedule_sync_required?
OpenFoodNetwork::ProxyOrderSyncer.new(subscriptions_to_sync).sync!
OrderManagement::Subscriptions::ProxyOrderSyncer.new(subscriptions_to_sync).sync!
end
def schedule_sync_required?

View File

@@ -1,4 +1,4 @@
require 'open_food_network/proxy_order_syncer'
require 'order_management/subscriptions/proxy_order_syncer'
class SubscriptionForm
attr_accessor :subscription, :params, :order_update_issues, :validator, :order_syncer, :estimator
@@ -29,6 +29,6 @@ class SubscriptionForm
private
def proxy_order_syncer
OpenFoodNetwork::ProxyOrderSyncer.new(subscription)
OrderManagement::Subscriptions::ProxyOrderSyncer.new(subscription)
end
end

View File

@@ -0,0 +1,99 @@
# frozen_string_literal: false
module OrderManagement
module Subscriptions
class ProxyOrderSyncer
attr_reader :subscription
delegate :order_cycles, :proxy_orders, :begins_at, :ends_at, to: :subscription
def initialize(subscriptions)
case subscriptions
when Subscription
@subscription = subscriptions
when ActiveRecord::Relation
@subscriptions = subscriptions.not_ended.not_canceled
else
raise "ProxyOrderSyncer must be initialized with " \
"an instance of Subscription or ActiveRecord::Relation"
end
end
def sync!
return sync_subscriptions! if @subscriptions
return initialise_proxy_orders! unless @subscription.id
sync_subscription!
end
private
def sync_subscriptions!
@subscriptions.each do |subscription|
@subscription = subscription
sync_subscription!
end
end
def initialise_proxy_orders!
uninitialised_order_cycle_ids.each do |order_cycle_id|
Rails.logger.info "Initializing Proxy Order " \
"of subscription #{@subscription.id} in order cycle #{order_cycle_id}"
proxy_orders << ProxyOrder.new(subscription: subscription, order_cycle_id: order_cycle_id)
end
end
def sync_subscription!
Rails.logger.info "Syncing Proxy Orders of subscription #{@subscription.id}"
create_proxy_orders!
remove_orphaned_proxy_orders!
end
def create_proxy_orders!
return unless not_closed_in_range_order_cycles.any?
query = "INSERT INTO proxy_orders (subscription_id, order_cycle_id, updated_at, created_at)"
query << " VALUES #{insert_values}"
query << " ON CONFLICT DO NOTHING"
ActiveRecord::Base.connection.exec_query(query)
end
def uninitialised_order_cycle_ids
not_closed_in_range_order_cycles.pluck(:id) - proxy_orders.map(&:order_cycle_id)
end
def remove_orphaned_proxy_orders!
orphaned_proxy_orders.scoped.delete_all
end
# Remove Proxy Orders that have not been placed yet
# and are in Order Cycles that are out of range
def orphaned_proxy_orders
orphaned = proxy_orders.where(placed_at: nil)
order_cycle_ids = in_range_order_cycles.pluck(:id)
return orphaned unless order_cycle_ids.any?
orphaned.where('order_cycle_id NOT IN (?)', order_cycle_ids)
end
def insert_values
now = Time.now.utc.iso8601
not_closed_in_range_order_cycles
.map{ |oc| "(#{subscription.id},#{oc.id},'#{now}','#{now}')" }
.join(",")
end
def not_closed_in_range_order_cycles
in_range_order_cycles.merge(OrderCycle.not_closed)
end
def in_range_order_cycles
order_cycles.where("orders_close_at >= ? AND orders_close_at <= ?",
begins_at,
ends_at || 100.years.from_now)
end
end
end
end

View File

@@ -0,0 +1,69 @@
# frozen_string_literal: true
module OrderManagement
module Subscriptions
describe ProxyOrderSyncer, performance: true do
let(:start) { Time.zone.now.beginning_of_day }
let!(:schedule) { create(:schedule, order_cycles: order_cycles) }
let!(:order_cycles) do
Array.new(10) do |i|
create(:simple_order_cycle, orders_open_at: start + i.days,
orders_close_at: start + (i + 1).days )
end
end
let!(:subscriptions) do
Array.new(150) do |_i|
create(:subscription, schedule: schedule, begins_at: start, ends_at: start + 10.days)
end
Subscription.where(schedule_id: schedule)
end
context "measuring performance for initialisation" do
it "reports the average run time for adding 10 OCs to 150 subscriptions" do
expect(ProxyOrder.count).to be 0
times = []
10.times do
syncer = ProxyOrderSyncer.new(subscriptions.reload)
t1 = Time.zone.now
syncer.sync!
t2 = Time.zone.now
diff = t2 - t1
times << diff
puts diff.round(2)
expect(ProxyOrder.count).to be 1500
ProxyOrder.destroy_all
end
puts "AVG: #{(times.sum / times.count).round(2)}"
end
end
context "measuring performance for removal" do
it "reports the average run time for removing 8 OCs from 150 subscriptions" do
times = []
10.times do
syncer = ProxyOrderSyncer.new(subscriptions.reload)
syncer.sync!
expect(ProxyOrder.count).to be 1500
subscriptions.update_all(begins_at: start + 8.days + 1.minute)
syncer = ProxyOrderSyncer.new(subscriptions.reload)
t1 = Time.zone.now
syncer.sync!
t2 = Time.zone.now
diff = t2 - t1
times << diff
puts diff.round(2)
expect(ProxyOrder.count).to be 300
subscriptions.update_all(begins_at: start)
end
puts "AVG: #{(times.sum / times.count).round(2)}"
end
end
end
end
end

View File

@@ -0,0 +1,402 @@
# frozen_string_literal: true
module OrderManagement
module Subscriptions
describe ProxyOrderSyncer do
describe "initialization" do
let!(:subscription) { create(:subscription) }
it "raises an error when initialized with an object that is not a Subscription or an ActiveRecord::Relation" do
expect{ ProxyOrderSyncer.new(subscription) }.to_not raise_error
expect{ ProxyOrderSyncer.new(Subscription.where(id: subscription.id)) }.to_not raise_error
expect{ ProxyOrderSyncer.new("something") }.to raise_error RuntimeError
end
end
describe "#sync!" do
let(:now) { Time.zone.now }
let(:schedule) { create(:schedule) }
let(:closed_oc) { create(:simple_order_cycle, schedules: [schedule], orders_open_at: now - 1.minute, orders_close_at: now) } # Closed
let(:open_oc_closes_before_begins_at_oc) { create(:simple_order_cycle, schedules: [schedule], orders_open_at: now - 1.minute, orders_close_at: now + 59.seconds) } # Open, but closes before begins at
let(:open_oc) { create(:simple_order_cycle, schedules: [schedule], orders_open_at: now - 1.minute, orders_close_at: now + 90.seconds) } # Open & closes between begins at and ends at
let(:upcoming_closes_before_begins_at_oc) { create(:simple_order_cycle, schedules: [schedule], orders_open_at: now + 30.seconds, orders_close_at: now + 59.seconds) } # Upcoming, but closes before begins at
let(:upcoming_closes_on_begins_at_oc) { create(:simple_order_cycle, schedules: [schedule], orders_open_at: now + 30.seconds, orders_close_at: now + 1.minute) } # Upcoming & closes on begins at
let(:upcoming_closes_on_ends_at_oc) { create(:simple_order_cycle, schedules: [schedule], orders_open_at: now + 30.seconds, orders_close_at: now + 2.minutes) } # Upcoming & closes on ends at
let(:upcoming_closes_after_ends_at_oc) { create(:simple_order_cycle, schedules: [schedule], orders_open_at: now + 30.seconds, orders_close_at: now + 121.seconds) } # Upcoming & closes after ends at
let(:subscription) { build(:subscription, schedule: schedule, begins_at: now + 1.minute, ends_at: now + 2.minutes) }
let(:proxy_orders) { subscription.reload.proxy_orders }
let(:order_cycles) { proxy_orders.map(&:order_cycle) }
let(:syncer) { ProxyOrderSyncer.new(subscription) }
context "when the subscription is not persisted" do
before do
oc # Ensure oc is created before we attempt to sync
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(0)
end
context "and the schedule includes a closed oc (ie. closed before opens_at)" do
let(:oc) { closed_oc }
it "does not create a new proxy order for that oc" do
expect{ subscription.save! }.to_not change(ProxyOrder, :count).from(0)
expect(order_cycles).to_not include oc
end
end
context "and the schedule includes an open oc that closes before begins_at" do
let(:oc) { open_oc_closes_before_begins_at_oc }
it "does not create a new proxy order for that oc" do
expect{ subscription.save! }.to_not change(ProxyOrder, :count).from(0)
expect(order_cycles).to_not include oc
end
end
context "and the schedule includes an open oc that closes between begins_at and ends_at" do
let(:oc) { open_oc }
it "creates a new proxy order for that oc" do
expect{ subscription.save! }.to change(ProxyOrder, :count).from(0).to(1)
expect(order_cycles).to include oc
end
end
context "and the schedule includes upcoming oc that closes before begins_at" do
let(:oc) { upcoming_closes_before_begins_at_oc }
it "does not create a new proxy order for that oc" do
expect{ subscription.save! }.to_not change(ProxyOrder, :count).from(0)
expect(order_cycles).to_not include oc
end
end
context "and the schedule includes upcoming oc that closes on begins_at" do
let(:oc) { upcoming_closes_on_begins_at_oc }
it "creates a new proxy order for that oc" do
expect{ subscription.save! }.to change(ProxyOrder, :count).from(0).to(1)
expect(order_cycles).to include oc
end
end
context "and the schedule includes upcoming oc that closes after ends_at" do
let(:oc) { upcoming_closes_on_ends_at_oc }
it "creates a new proxy order for that oc" do
expect{ subscription.save! }.to change(ProxyOrder, :count).from(0).to(1)
expect(order_cycles).to include oc
end
end
context "and the schedule includes upcoming oc that closes after ends_at" do
let(:oc) { upcoming_closes_after_ends_at_oc }
it "does not create a new proxy order for that oc" do
expect{ subscription.save! }.to_not change(ProxyOrder, :count).from(0)
expect(order_cycles).to_not include oc
end
end
end
context "when the subscription is persisted" do
before { expect(subscription.save!).to be true }
context "when a proxy order exists" do
let!(:proxy_order) { create(:proxy_order, subscription: subscription, order_cycle: oc) }
context "for an oc included in the relevant schedule" do
context "and the proxy order has already been placed" do
before { proxy_order.update_attributes(placed_at: 5.minutes.ago) }
context "the oc is closed (ie. closed before opens_at)" do
let(:oc) { closed_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the schedule includes an open oc that closes before begins_at" do
let(:oc) { open_oc_closes_before_begins_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is open and closes between begins_at and ends_at" do
let(:oc) { open_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes before begins_at" do
let(:oc) { upcoming_closes_before_begins_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes on begins_at" do
let(:oc) { upcoming_closes_on_begins_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes on ends_at" do
let(:oc) { upcoming_closes_on_ends_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes after ends_at" do
let(:oc) { upcoming_closes_after_ends_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
end
context "and the proxy order has not already been placed" do
context "the oc is closed (ie. closed before opens_at)" do
let(:oc) { closed_oc }
it "removes the proxy order" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(1).to(0)
expect(proxy_orders).to_not include proxy_order
end
end
context "and the schedule includes an open oc that closes before begins_at" do
let(:oc) { open_oc_closes_before_begins_at_oc }
it "removes the proxy order" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(1).to(0)
expect(proxy_orders).to_not include proxy_order
end
end
context "and the oc is open and closes between begins_at and ends_at" do
let(:oc) { open_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes before begins_at" do
let(:oc) { upcoming_closes_before_begins_at_oc }
it "removes the proxy order" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(1).to(0)
expect(proxy_orders).to_not include proxy_order
end
end
context "and the oc is upcoming and closes on begins_at" do
let(:oc) { upcoming_closes_on_begins_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes on ends_at" do
let(:oc) { upcoming_closes_on_ends_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes after ends_at" do
let(:oc) { upcoming_closes_after_ends_at_oc }
it "removes the proxy order" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(1).to(0)
expect(proxy_orders).to_not include proxy_order
end
end
end
end
context "for an oc not included in the relevant schedule" do
let!(:proxy_order) { create(:proxy_order, subscription: subscription, order_cycle: open_oc) }
before do
open_oc.schedule_ids = []
expect(open_oc.save!).to be true
end
context "and the proxy order has already been placed" do
before { proxy_order.update_attributes(placed_at: 5.minutes.ago) }
context "the oc is closed (ie. closed before opens_at)" do
let(:oc) { closed_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the schedule includes an open oc that closes before begins_at" do
let(:oc) { open_oc_closes_before_begins_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is open and closes between begins_at and ends_at" do
let(:oc) { open_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes before begins_at" do
let(:oc) { upcoming_closes_before_begins_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes on begins_at" do
let(:oc) { upcoming_closes_on_begins_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes on ends_at" do
let(:oc) { upcoming_closes_on_ends_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes after ends_at" do
let(:oc) { upcoming_closes_after_ends_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
end
context "and the proxy order has not already been placed" do
# This shouldn't really happen, but it is possible
context "the oc is closed (ie. closed before opens_at)" do
let(:oc) { closed_oc }
it "removes the proxy order" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(1).to(0)
expect(proxy_orders).to_not include proxy_order
end
end
# This shouldn't really happen, but it is possible
context "and the oc is open and closes between begins_at and ends_at" do
let(:oc) { open_oc }
it "removes the proxy order" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(1).to(0)
expect(proxy_orders).to_not include proxy_order
end
end
context "and the oc is upcoming and closes before begins_at" do
let(:oc) { upcoming_closes_before_begins_at_oc }
it "removes the proxy order" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(1).to(0)
expect(proxy_orders).to_not include proxy_order
end
end
context "and the oc is upcoming and closes on begins_at" do
let(:oc) { upcoming_closes_on_begins_at_oc }
it "removes the proxy order" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(1).to(0)
expect(proxy_orders).to_not include proxy_order
end
end
context "and the oc is upcoming and closes on ends_at" do
let(:oc) { upcoming_closes_on_ends_at_oc }
it "removes the proxy order" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(1).to(0)
expect(proxy_orders).to_not include proxy_order
end
end
context "and the oc is upcoming and closes after ends_at" do
let(:oc) { upcoming_closes_after_ends_at_oc }
it "removes the proxy order" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(1).to(0)
expect(proxy_orders).to_not include proxy_order
end
end
end
end
end
context "when a proxy order does not exist" do
context "and the schedule includes a closed oc (ie. closed before opens_at)" do
let!(:oc) { closed_oc }
it "does not create a new proxy order for that oc" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(0)
expect(order_cycles).to_not include oc
end
end
context "and the schedule includes an open oc that closes before begins_at" do
let(:oc) { open_oc_closes_before_begins_at_oc }
it "does not create a new proxy order for that oc" do
expect{ subscription.save! }.to_not change(ProxyOrder, :count).from(0)
expect(order_cycles).to_not include oc
end
end
context "and the schedule includes an open oc that closes between begins_at and ends_at" do
let!(:oc) { open_oc }
it "creates a new proxy order for that oc" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(0).to(1)
expect(order_cycles).to include oc
end
end
context "and the schedule includes upcoming oc that closes before begins_at" do
let!(:oc) { upcoming_closes_before_begins_at_oc }
it "does not create a new proxy order for that oc" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(0)
expect(order_cycles).to_not include oc
end
end
context "and the schedule includes upcoming oc that closes on begins_at" do
let!(:oc) { upcoming_closes_on_begins_at_oc }
it "creates a new proxy order for that oc" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(0).to(1)
expect(order_cycles).to include oc
end
end
context "and the schedule includes upcoming oc that closes on ends_at" do
let!(:oc) { upcoming_closes_on_ends_at_oc }
it "creates a new proxy order for that oc" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(0).to(1)
expect(order_cycles).to include oc
end
end
context "and the schedule includes upcoming oc that closes after ends_at" do
let!(:oc) { upcoming_closes_after_ends_at_oc }
it "does not create a new proxy order for that oc" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(0)
expect(order_cycles).to_not include oc
end
end
end
end
end
end
end
end

View File

@@ -1,95 +0,0 @@
module OpenFoodNetwork
class ProxyOrderSyncer
attr_reader :subscription
delegate :order_cycles, :proxy_orders, :begins_at, :ends_at, to: :subscription
def initialize(subscriptions)
case subscriptions
when Subscription
@subscription = subscriptions
when ActiveRecord::Relation
@subscriptions = subscriptions.not_ended.not_canceled
else
raise "ProxyOrderSyncer must be initialized with " \
"an instance of Subscription or ActiveRecord::Relation"
end
end
def sync!
return sync_subscriptions! if @subscriptions
return initialise_proxy_orders! unless @subscription.id
sync_subscription!
end
private
def sync_subscriptions!
@subscriptions.each do |subscription|
@subscription = subscription
sync_subscription!
end
end
def initialise_proxy_orders!
uninitialised_order_cycle_ids.each do |order_cycle_id|
Rails.logger.info "Initializing Proxy Order " \
"of subscription #{@subscription.id} in order cycle #{order_cycle_id}"
proxy_orders << ProxyOrder.new(subscription: subscription, order_cycle_id: order_cycle_id)
end
end
def sync_subscription!
Rails.logger.info "Syncing Proxy Orders of subscription #{@subscription.id}"
create_proxy_orders!
remove_orphaned_proxy_orders!
end
def create_proxy_orders!
return unless not_closed_in_range_order_cycles.any?
query = "INSERT INTO proxy_orders (subscription_id, order_cycle_id, updated_at, created_at)"
query << " VALUES #{insert_values}"
query << " ON CONFLICT DO NOTHING"
ActiveRecord::Base.connection.exec_query(query)
end
def uninitialised_order_cycle_ids
not_closed_in_range_order_cycles.pluck(:id) - proxy_orders.map(&:order_cycle_id)
end
def remove_orphaned_proxy_orders!
orphaned_proxy_orders.scoped.delete_all
end
# Remove Proxy Orders that have not been placed yet
# and are in Order Cycles that are out of range
def orphaned_proxy_orders
orphaned = proxy_orders.where(placed_at: nil)
order_cycle_ids = in_range_order_cycles.pluck(:id)
return orphaned unless order_cycle_ids.any?
orphaned.where('order_cycle_id NOT IN (?)', order_cycle_ids)
end
def insert_values
now = Time.now.utc.iso8601
not_closed_in_range_order_cycles
.map{ |oc| "(#{subscription.id},#{oc.id},'#{now}','#{now}')" }
.join(",")
end
def not_closed_in_range_order_cycles
in_range_order_cycles.merge(OrderCycle.not_closed)
end
def in_range_order_cycles
order_cycles.where("orders_close_at >= ? AND orders_close_at <= ?",
begins_at,
ends_at || 100.years.from_now)
end
end
end

View File

@@ -90,7 +90,7 @@ describe Admin::SchedulesController, type: :controller do
it "syncs proxy orders when order_cycle_ids change" do
syncer_mock = double(:syncer)
allow(OpenFoodNetwork::ProxyOrderSyncer).to receive(:new) { syncer_mock }
allow(OrderManagement::Subscriptions::ProxyOrderSyncer).to receive(:new) { syncer_mock }
expect(syncer_mock).to receive(:sync!).exactly(2).times
spree_put :update, format: :json, id: coordinated_schedule.id, schedule: { order_cycle_ids: [coordinated_order_cycle.id, coordinated_order_cycle2.id] }
@@ -150,7 +150,7 @@ describe Admin::SchedulesController, type: :controller do
it "sync proxy orders" do
syncer_mock = double(:syncer)
allow(OpenFoodNetwork::ProxyOrderSyncer).to receive(:new) { syncer_mock }
allow(OrderManagement::Subscriptions::ProxyOrderSyncer).to receive(:new) { syncer_mock }
expect(syncer_mock).to receive(:sync!).once
create_schedule params

View File

@@ -1,400 +0,0 @@
require 'open_food_network/proxy_order_syncer'
module OpenFoodNetwork
describe ProxyOrderSyncer do
describe "initialization" do
let!(:subscription) { create(:subscription) }
it "raises an error when initialized with an object that is not a Subscription or an ActiveRecord::Relation" do
expect{ ProxyOrderSyncer.new(subscription) }.to_not raise_error
expect{ ProxyOrderSyncer.new(Subscription.where(id: subscription.id)) }.to_not raise_error
expect{ ProxyOrderSyncer.new("something") }.to raise_error RuntimeError
end
end
describe "#sync!" do
let(:now) { Time.zone.now }
let(:schedule) { create(:schedule) }
let(:closed_oc) { create(:simple_order_cycle, schedules: [schedule], orders_open_at: now - 1.minute, orders_close_at: now) } # Closed
let(:open_oc_closes_before_begins_at_oc) { create(:simple_order_cycle, schedules: [schedule], orders_open_at: now - 1.minute, orders_close_at: now + 59.seconds) } # Open, but closes before begins at
let(:open_oc) { create(:simple_order_cycle, schedules: [schedule], orders_open_at: now - 1.minute, orders_close_at: now + 90.seconds) } # Open & closes between begins at and ends at
let(:upcoming_closes_before_begins_at_oc) { create(:simple_order_cycle, schedules: [schedule], orders_open_at: now + 30.seconds, orders_close_at: now + 59.seconds) } # Upcoming, but closes before begins at
let(:upcoming_closes_on_begins_at_oc) { create(:simple_order_cycle, schedules: [schedule], orders_open_at: now + 30.seconds, orders_close_at: now + 1.minute) } # Upcoming & closes on begins at
let(:upcoming_closes_on_ends_at_oc) { create(:simple_order_cycle, schedules: [schedule], orders_open_at: now + 30.seconds, orders_close_at: now + 2.minutes) } # Upcoming & closes on ends at
let(:upcoming_closes_after_ends_at_oc) { create(:simple_order_cycle, schedules: [schedule], orders_open_at: now + 30.seconds, orders_close_at: now + 121.seconds) } # Upcoming & closes after ends at
let(:subscription) { build(:subscription, schedule: schedule, begins_at: now + 1.minute, ends_at: now + 2.minutes) }
let(:proxy_orders) { subscription.reload.proxy_orders }
let(:order_cycles) { proxy_orders.map(&:order_cycle) }
let(:syncer) { ProxyOrderSyncer.new(subscription) }
context "when the subscription is not persisted" do
before do
oc # Ensure oc is created before we attempt to sync
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(0)
end
context "and the schedule includes a closed oc (ie. closed before opens_at)" do
let(:oc) { closed_oc }
it "does not create a new proxy order for that oc" do
expect{ subscription.save! }.to_not change(ProxyOrder, :count).from(0)
expect(order_cycles).to_not include oc
end
end
context "and the schedule includes an open oc that closes before begins_at" do
let(:oc) { open_oc_closes_before_begins_at_oc }
it "does not create a new proxy order for that oc" do
expect{ subscription.save! }.to_not change(ProxyOrder, :count).from(0)
expect(order_cycles).to_not include oc
end
end
context "and the schedule includes an open oc that closes between begins_at and ends_at" do
let(:oc) { open_oc }
it "creates a new proxy order for that oc" do
expect{ subscription.save! }.to change(ProxyOrder, :count).from(0).to(1)
expect(order_cycles).to include oc
end
end
context "and the schedule includes upcoming oc that closes before begins_at" do
let(:oc) { upcoming_closes_before_begins_at_oc }
it "does not create a new proxy order for that oc" do
expect{ subscription.save! }.to_not change(ProxyOrder, :count).from(0)
expect(order_cycles).to_not include oc
end
end
context "and the schedule includes upcoming oc that closes on begins_at" do
let(:oc) { upcoming_closes_on_begins_at_oc }
it "creates a new proxy order for that oc" do
expect{ subscription.save! }.to change(ProxyOrder, :count).from(0).to(1)
expect(order_cycles).to include oc
end
end
context "and the schedule includes upcoming oc that closes after ends_at" do
let(:oc) { upcoming_closes_on_ends_at_oc }
it "creates a new proxy order for that oc" do
expect{ subscription.save! }.to change(ProxyOrder, :count).from(0).to(1)
expect(order_cycles).to include oc
end
end
context "and the schedule includes upcoming oc that closes after ends_at" do
let(:oc) { upcoming_closes_after_ends_at_oc }
it "does not create a new proxy order for that oc" do
expect{ subscription.save! }.to_not change(ProxyOrder, :count).from(0)
expect(order_cycles).to_not include oc
end
end
end
context "when the subscription is persisted" do
before { expect(subscription.save!).to be true }
context "when a proxy order exists" do
let!(:proxy_order) { create(:proxy_order, subscription: subscription, order_cycle: oc) }
context "for an oc included in the relevant schedule" do
context "and the proxy order has already been placed" do
before { proxy_order.update_attributes(placed_at: 5.minutes.ago) }
context "the oc is closed (ie. closed before opens_at)" do
let(:oc) { closed_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the schedule includes an open oc that closes before begins_at" do
let(:oc) { open_oc_closes_before_begins_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is open and closes between begins_at and ends_at" do
let(:oc) { open_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes before begins_at" do
let(:oc) { upcoming_closes_before_begins_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes on begins_at" do
let(:oc) { upcoming_closes_on_begins_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes on ends_at" do
let(:oc) { upcoming_closes_on_ends_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes after ends_at" do
let(:oc) { upcoming_closes_after_ends_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
end
context "and the proxy order has not already been placed" do
context "the oc is closed (ie. closed before opens_at)" do
let(:oc) { closed_oc }
it "removes the proxy order" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(1).to(0)
expect(proxy_orders).to_not include proxy_order
end
end
context "and the schedule includes an open oc that closes before begins_at" do
let(:oc) { open_oc_closes_before_begins_at_oc }
it "removes the proxy order" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(1).to(0)
expect(proxy_orders).to_not include proxy_order
end
end
context "and the oc is open and closes between begins_at and ends_at" do
let(:oc) { open_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes before begins_at" do
let(:oc) { upcoming_closes_before_begins_at_oc }
it "removes the proxy order" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(1).to(0)
expect(proxy_orders).to_not include proxy_order
end
end
context "and the oc is upcoming and closes on begins_at" do
let(:oc) { upcoming_closes_on_begins_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes on ends_at" do
let(:oc) { upcoming_closes_on_ends_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes after ends_at" do
let(:oc) { upcoming_closes_after_ends_at_oc }
it "removes the proxy order" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(1).to(0)
expect(proxy_orders).to_not include proxy_order
end
end
end
end
context "for an oc not included in the relevant schedule" do
let!(:proxy_order) { create(:proxy_order, subscription: subscription, order_cycle: open_oc) }
before do
open_oc.schedule_ids = []
expect(open_oc.save!).to be true
end
context "and the proxy order has already been placed" do
before { proxy_order.update_attributes(placed_at: 5.minutes.ago) }
context "the oc is closed (ie. closed before opens_at)" do
let(:oc) { closed_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the schedule includes an open oc that closes before begins_at" do
let(:oc) { open_oc_closes_before_begins_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is open and closes between begins_at and ends_at" do
let(:oc) { open_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes before begins_at" do
let(:oc) { upcoming_closes_before_begins_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes on begins_at" do
let(:oc) { upcoming_closes_on_begins_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes on ends_at" do
let(:oc) { upcoming_closes_on_ends_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
context "and the oc is upcoming and closes after ends_at" do
let(:oc) { upcoming_closes_after_ends_at_oc }
it "keeps the proxy order" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(1)
expect(proxy_orders).to include proxy_order
end
end
end
context "and the proxy order has not already been placed" do
# This shouldn't really happen, but it is possible
context "the oc is closed (ie. closed before opens_at)" do
let(:oc) { closed_oc }
it "removes the proxy order" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(1).to(0)
expect(proxy_orders).to_not include proxy_order
end
end
# This shouldn't really happen, but it is possible
context "and the oc is open and closes between begins_at and ends_at" do
let(:oc) { open_oc }
it "removes the proxy order" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(1).to(0)
expect(proxy_orders).to_not include proxy_order
end
end
context "and the oc is upcoming and closes before begins_at" do
let(:oc) { upcoming_closes_before_begins_at_oc }
it "removes the proxy order" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(1).to(0)
expect(proxy_orders).to_not include proxy_order
end
end
context "and the oc is upcoming and closes on begins_at" do
let(:oc) { upcoming_closes_on_begins_at_oc }
it "removes the proxy order" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(1).to(0)
expect(proxy_orders).to_not include proxy_order
end
end
context "and the oc is upcoming and closes on ends_at" do
let(:oc) { upcoming_closes_on_ends_at_oc }
it "removes the proxy order" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(1).to(0)
expect(proxy_orders).to_not include proxy_order
end
end
context "and the oc is upcoming and closes after ends_at" do
let(:oc) { upcoming_closes_after_ends_at_oc }
it "removes the proxy order" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(1).to(0)
expect(proxy_orders).to_not include proxy_order
end
end
end
end
end
context "when a proxy order does not exist" do
context "and the schedule includes a closed oc (ie. closed before opens_at)" do
let!(:oc) { closed_oc }
it "does not create a new proxy order for that oc" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(0)
expect(order_cycles).to_not include oc
end
end
context "and the schedule includes an open oc that closes before begins_at" do
let(:oc) { open_oc_closes_before_begins_at_oc }
it "does not create a new proxy order for that oc" do
expect{ subscription.save! }.to_not change(ProxyOrder, :count).from(0)
expect(order_cycles).to_not include oc
end
end
context "and the schedule includes an open oc that closes between begins_at and ends_at" do
let!(:oc) { open_oc }
it "creates a new proxy order for that oc" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(0).to(1)
expect(order_cycles).to include oc
end
end
context "and the schedule includes upcoming oc that closes before begins_at" do
let!(:oc) { upcoming_closes_before_begins_at_oc }
it "does not create a new proxy order for that oc" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(0)
expect(order_cycles).to_not include oc
end
end
context "and the schedule includes upcoming oc that closes on begins_at" do
let!(:oc) { upcoming_closes_on_begins_at_oc }
it "creates a new proxy order for that oc" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(0).to(1)
expect(order_cycles).to include oc
end
end
context "and the schedule includes upcoming oc that closes on ends_at" do
let!(:oc) { upcoming_closes_on_ends_at_oc }
it "creates a new proxy order for that oc" do
expect{ syncer.sync! }.to change(ProxyOrder, :count).from(0).to(1)
expect(order_cycles).to include oc
end
end
context "and the schedule includes upcoming oc that closes after ends_at" do
let!(:oc) { upcoming_closes_after_ends_at_oc }
it "does not create a new proxy order for that oc" do
expect{ syncer.sync! }.to_not change(ProxyOrder, :count).from(0)
expect(order_cycles).to_not include oc
end
end
end
end
end
end
end

View File

@@ -1,67 +0,0 @@
require 'open_food_network/proxy_order_syncer'
module OpenFoodNetwork
describe ProxyOrderSyncer, performance: true do
let(:start) { Time.zone.now.beginning_of_day }
let!(:schedule) { create(:schedule, order_cycles: order_cycles) }
let!(:order_cycles) do
Array.new(10) do |i|
create(:simple_order_cycle, orders_open_at: start + i.days,
orders_close_at: start + (i + 1).days )
end
end
let!(:subscriptions) do
Array.new(150) do |_i|
create(:subscription, schedule: schedule, begins_at: start, ends_at: start + 10.days)
end
Subscription.where(schedule_id: schedule)
end
context "measuring performance for initialisation" do
it "reports the average run time for adding 10 OCs to 150 subscriptions" do
expect(ProxyOrder.count).to be 0
times = []
10.times do
syncer = ProxyOrderSyncer.new(subscriptions.reload)
t1 = Time.zone.now
syncer.sync!
t2 = Time.zone.now
diff = t2 - t1
times << diff
puts diff.round(2)
expect(ProxyOrder.count).to be 1500
ProxyOrder.destroy_all
end
puts "AVG: #{(times.sum / times.count).round(2)}"
end
end
context "measuring performance for removal" do
it "reports the average run time for removing 8 OCs from 150 subscriptions" do
times = []
10.times do
syncer = ProxyOrderSyncer.new(subscriptions.reload)
syncer.sync!
expect(ProxyOrder.count).to be 1500
subscriptions.update_all(begins_at: start + 8.days + 1.minute)
syncer = ProxyOrderSyncer.new(subscriptions.reload)
t1 = Time.zone.now
syncer.sync!
t2 = Time.zone.now
diff = t2 - t1
times << diff
puts diff.round(2)
expect(ProxyOrder.count).to be 300
subscriptions.update_all(begins_at: start)
end
puts "AVG: #{(times.sum / times.count).round(2)}"
end
end
end
end

View File

@@ -1,3 +1,5 @@
require 'order_management/subscriptions/proxy_order_syncer'
describe OrderCycleForm do
describe "save" do
describe "creating a new order cycle from params" do
@@ -66,10 +68,10 @@ describe OrderCycleForm do
context "where I manage the order_cycle's coordinator" do
let(:form) { OrderCycleForm.new(coordinated_order_cycle, params, user) }
let(:syncer_mock) { instance_double(OpenFoodNetwork::ProxyOrderSyncer, sync!: true) }
let(:syncer_mock) { instance_double(OrderManagement::Subscriptions::ProxyOrderSyncer, sync!: true) }
before do
allow(OpenFoodNetwork::ProxyOrderSyncer).to receive(:new) { syncer_mock }
allow(OrderManagement::Subscriptions::ProxyOrderSyncer).to receive(:new) { syncer_mock }
end
context "and I add an schedule that I own, and remove another that I own" do