summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorAlessandro Desantis <desa.alessandro@gmail.com>2020-11-19 13:13:46 +0100
committerAlessandro Desantis <desa.alessandro@gmail.com>2021-01-20 10:46:01 +0100
commit5081ffc17562fa90edf0f2a8dfa0d03991480362 (patch)
treec0933ca52e80bf93b73e7387d5eee1b830a8099b /lib
parent69f0ca038b66d0ca36971405e51c0d3aa916cf19 (diff)
Streamline and simplify `SolidusSubscriptions::Processor`
The processor was way too complicated, taking on too many responsibilities. As a result, it was complex to reason about and it was a very brittle part of the extension. The new processor simply does two things: it finds all actionable subscriptions and ensures to take the appropriate action on them (e.g., cancel, deactivate), including creating the new installment for later. Then, it finds all the actionable installments (including the ones that were just created), and schedules them for asynchronous processing. One side effect of this refactoring is that installments are not grouped by address and payment method/source anymore: each installment will always correspond to a new order. Any logistics-related optimizations should be implemented by the individual store.
Diffstat (limited to 'lib')
-rw-r--r--lib/solidus_subscriptions/processor.rb110
-rw-r--r--lib/solidus_subscriptions/testing_support/factories/installment_factory.rb4
2 files changed, 19 insertions, 95 deletions
diff --git a/lib/solidus_subscriptions/processor.rb b/lib/solidus_subscriptions/processor.rb
index d56816a..4547cec 100644
--- a/lib/solidus_subscriptions/processor.rb
+++ b/lib/solidus_subscriptions/processor.rb
@@ -1,116 +1,36 @@
# frozen_string_literal: true
-# This class is responsible for finding subscriptions and installments
-# which need to be processed. It will group them together by user and attempts
-# to process them together. Subscriptions will also be grouped by their
-# shiping address id.
-#
-# This class passes the reponsibility of actually creating the order off onto
-# the consolidated installment class.
-#
-# This class generates `ProcessInstallmentsJob`s
module SolidusSubscriptions
class Processor
class << self
- # Find all actionable subscriptions and intallments, group them together
- # by user, and schedule a processing job for the group as a batch
def run
- batched_users_to_be_processed.each { |batch| new(batch).build_jobs }
+ SolidusSubscriptions::Subscription.actionable.find_each(&method(:process_subscription))
+ SolidusSubscriptions::Installment.actionable.with_active_subscription.find_each(&method(:process_installment))
end
private
- def batched_users_to_be_processed
- subscriptions = SolidusSubscriptions::Subscription.arel_table
- installments = SolidusSubscriptions::Installment.arel_table
+ def process_subscription(subscription)
+ ActiveRecord::Base.transaction do
+ subscription.successive_skip_count = 0
+ subscription.advance_actionable_date
- ::Spree.user_class.
- joins(:subscriptions).
- joins(
- subscriptions.
- join(installments, Arel::Nodes::OuterJoin).
- on(subscriptions[:id].eq(installments[:subscription_id])).
- join_sources
- ).
- where(
- SolidusSubscriptions::Subscription.actionable.arel.constraints.reduce(:and).
- or(SolidusSubscriptions::Installment.actionable.with_active_subscription.arel.constraints.reduce(:and))
- ).
- distinct.
- find_in_batches
- end
- end
-
- # @return [Array<Spree.user_class>]
- attr_reader :users
-
- # Get a new instance of the SolidusSubscriptions::Processor
- #
- # @param users [Array<Spree.user_class>] A list of users with actionable
- # subscriptions or installments
- #
- # @return [SolidusSubscriptions::Processor]
- def initialize(users)
- @users = users
- @installments = {}
- end
-
- # Create `ProcessInstallmentsJob`s for the users used to initalize the
- # instance
- def build_jobs
- users.map do |user|
- installemts_by_address_and_user = installments(user).group_by do |i|
- [i.subscription.shipping_address_id, i.subscription.billing_address_id]
- end
-
- installemts_by_address_and_user.each_value do |grouped_installments|
- ProcessInstallmentsJob.perform_later grouped_installments.map(&:id)
- end
- end
- end
+ subscription.cancel! if subscription.pending_cancellation?
+ subscription.deactivate! if subscription.can_be_deactivated?
- private
-
- def subscriptions_by_id
- @subscriptions_by_id ||= Subscription.
- actionable.
- includes(:line_items, :user).
- where(user_id: user_ids).
- group_by(&:user_id)
- end
-
- def retry_installments
- @failed_installments ||= Installment.
- actionable.
- includes(:subscription).
- where(solidus_subscriptions_subscriptions: { user_id: user_ids }).
- group_by { |i| i.subscription.user_id }
- end
-
- def installments(user)
- @installments[user.id] ||= retry_installments.fetch(user.id, []) + new_installments(user)
- end
-
- def new_installments(user)
- ActiveRecord::Base.transaction do
- subscriptions_by_id.fetch(user.id, []).map do |sub|
- sub.successive_skip_count = 0
- sub.advance_actionable_date
- sub.cancel! if sub.pending_cancellation?
- sub.deactivate! if sub.can_be_deactivated?
if SolidusSubscriptions.configuration.clear_past_installments
- sub.installments.unfulfilled.each do |installment|
- installment.actionable_date = nil
- installment.save!
+ subscription.installments.unfulfilled.actionable.each do |installment|
+ installment.update!(actionable_date: nil)
end
end
- sub.installments.create!
+
+ subscription.installments.create!(actionable_date: Time.zone.now)
end
end
- end
- def user_ids
- @user_ids ||= users.map(&:id)
+ def process_installment(installment)
+ ProcessInstallmentsJob.perform_later(installment)
+ end
end
end
end
diff --git a/lib/solidus_subscriptions/testing_support/factories/installment_factory.rb b/lib/solidus_subscriptions/testing_support/factories/installment_factory.rb
index 2eb5069..20dcc04 100644
--- a/lib/solidus_subscriptions/testing_support/factories/installment_factory.rb
+++ b/lib/solidus_subscriptions/testing_support/factories/installment_factory.rb
@@ -21,5 +21,9 @@ FactoryBot.define do
[association(:installment_detail, :success, installment: @instance, order: order)]
end
end
+
+ trait :actionable do
+ actionable_date { Time.zone.now }
+ end
end
end