summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolò€ Rebughini <nicolo.rebughini@gmail.com>2021-02-26 10:06:09 +0100
committerNicolò€ Rebughini <nicolo.rebughini@gmail.com>2021-02-26 15:19:34 +0100
commit580fa7b2c62dba52fce7fb87def9eb1254507534 (patch)
tree601a11ab51054b776737e009e9000e86cb5b6d70
parentcfcb35e8b1211d7aac66c709610c1cdc707c5db8 (diff)
Move subscription processing to background jobs
This change moves the processing of individual subscriptions to their own isolated jobs. The tasks of these individual jobs will be: - Deactivate/Cancel subscriptions pending one of those operations - Clear any past failed installment if it's configured to do so - Create a new installment for the subscription cycle - Enqueue a job to process any actionable installment for the subscription Given we've moved the concern of queueing the installment processing, that part can be removed safely from the main processor job. The final part of the change also touches the main processor job, whose task is now to collect any actionable subscription and any subscription with an actionable installment to enqueue them for individual processing.
-rw-r--r--app/jobs/solidus_subscriptions/process_subscription_job.rb31
-rw-r--r--lib/solidus_subscriptions/processor.rb31
-rw-r--r--spec/jobs/solidus_subscriptions/process_subscription_job_spec.rb83
-rw-r--r--spec/lib/solidus_subscriptions/processor_spec.rb132
4 files changed, 140 insertions, 137 deletions
diff --git a/app/jobs/solidus_subscriptions/process_subscription_job.rb b/app/jobs/solidus_subscriptions/process_subscription_job.rb
new file mode 100644
index 0000000..569fa6f
--- /dev/null
+++ b/app/jobs/solidus_subscriptions/process_subscription_job.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module SolidusSubscriptions
+ class ProcessSubscriptionJob < ApplicationJob
+ queue_as { SolidusSubscriptions.configuration.processing_queue }
+
+ def perform(subscription)
+ ActiveRecord::Base.transaction do
+ if SolidusSubscriptions.configuration.clear_past_installments
+ subscription.installments.unfulfilled.actionable.each do |installment|
+ installment.update!(actionable_date: nil)
+ end
+ end
+
+ if subscription.actionable?
+ subscription.successive_skip_count = 0
+ subscription.advance_actionable_date
+
+ subscription.installments.create!(actionable_date: Time.zone.now)
+ end
+
+ subscription.cancel! if subscription.pending_cancellation?
+ subscription.deactivate! if subscription.can_be_deactivated?
+
+ subscription.installments.actionable.find_each do |installment|
+ SolidusSubscriptions::ProcessInstallmentJob.perform_later(installment)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/solidus_subscriptions/processor.rb b/lib/solidus_subscriptions/processor.rb
index ffc274b..04b84e8 100644
--- a/lib/solidus_subscriptions/processor.rb
+++ b/lib/solidus_subscriptions/processor.rb
@@ -4,32 +4,13 @@ module SolidusSubscriptions
class Processor
class << self
def run
- SolidusSubscriptions::Subscription.actionable.find_each(&method(:process_subscription))
- SolidusSubscriptions::Installment.actionable.find_each(&method(:process_installment))
- end
-
- private
-
- def process_subscription(subscription)
- ActiveRecord::Base.transaction do
- subscription.successive_skip_count = 0
- subscription.advance_actionable_date
-
- subscription.cancel! if subscription.pending_cancellation?
- subscription.deactivate! if subscription.can_be_deactivated?
-
- if SolidusSubscriptions.configuration.clear_past_installments
- subscription.installments.unfulfilled.actionable.each do |installment|
- installment.update!(actionable_date: nil)
- end
+ SolidusSubscriptions::Subscription
+ .where(installments: SolidusSubscriptions::Installment.actionable)
+ .or(SolidusSubscriptions::Subscription.actionable)
+ .distinct
+ .find_each do |subscription|
+ ProcessSubscriptionJob.perform_later(subscription)
end
-
- subscription.installments.create!(actionable_date: Time.zone.now)
- end
- end
-
- def process_installment(installment)
- ProcessInstallmentJob.perform_later(installment)
end
end
end
diff --git a/spec/jobs/solidus_subscriptions/process_subscription_job_spec.rb b/spec/jobs/solidus_subscriptions/process_subscription_job_spec.rb
new file mode 100644
index 0000000..1a3cf6f
--- /dev/null
+++ b/spec/jobs/solidus_subscriptions/process_subscription_job_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+RSpec.describe SolidusSubscriptions::ProcessSubscriptionJob do
+ context 'when clear_past_installments is enabled' do
+ it 'voids the actionable date of the unfulfilled installments' do
+ stub_config(clear_past_installments: true)
+ subscription = create(:subscription)
+ unfulfilled_installment = create(:installment, :failed, subscription: subscription)
+
+ described_class.perform_now(subscription)
+
+ expect(unfulfilled_installment.reload.actionable_date).to eq(nil)
+ end
+ end
+
+ context 'when the subscription is actionable' do
+ it 'resets the successive_skip_count' do
+ subscription = create(:subscription, :actionable, successive_skip_count: 3)
+
+ described_class.perform_now(subscription)
+
+ expect(subscription.reload.successive_skip_count).to eq(0)
+ end
+
+ it 'creates a new installment' do
+ subscription = create(:subscription, :actionable)
+
+ described_class.perform_now(subscription)
+
+ expect(subscription.installments.count).to eq(1)
+ end
+
+ it 'advances the actionable date' do
+ subscription = create(:subscription, :actionable)
+ subscription.update_columns(interval_length: 1, interval_units: 'week')
+ old_actionable_date = subscription.reload.actionable_date
+
+ described_class.perform_now(subscription)
+
+ expect(subscription.reload.actionable_date.to_date).to eq((old_actionable_date + 1.week).to_date)
+ end
+ end
+
+ context 'when the subscription is pending cancellation' do
+ it 'cancels the subscription' do
+ subscription = create(
+ :subscription,
+ :actionable,
+ :pending_cancellation,
+ )
+ described_class.perform_now(subscription)
+
+ expect(subscription.reload.state).to eq('canceled')
+ end
+ end
+
+ context 'when the subscription is pending of deactivation' do
+ it 'deactivates the subscription' do
+ subscription = create(
+ :subscription,
+ :actionable,
+ interval_units: 'week',
+ interval_length: 2,
+ end_date: 3.days.from_now,
+ )
+ described_class.perform_now(subscription)
+
+ expect(subscription.reload.state).to eq('inactive')
+ end
+ end
+
+ it 'schedules all the subscription actionable installments for processing' do
+ subscription = create(:subscription, :actionable)
+ unfulfilled_installment = create(:installment, :failed, subscription: subscription)
+
+ described_class.perform_now(subscription)
+
+ new_installment = subscription.reload.installments.last
+ [unfulfilled_installment, new_installment].each do |installment|
+ expect(SolidusSubscriptions::ProcessInstallmentJob).to have_been_enqueued.with(installment)
+ end
+ end
+end
diff --git a/spec/lib/solidus_subscriptions/processor_spec.rb b/spec/lib/solidus_subscriptions/processor_spec.rb
index e82c0e9..5deaba4 100644
--- a/spec/lib/solidus_subscriptions/processor_spec.rb
+++ b/spec/lib/solidus_subscriptions/processor_spec.rb
@@ -1,124 +1,32 @@
-RSpec.describe SolidusSubscriptions::Processor, :checkout do
- shared_examples 'processes the subscription' do
- it 'resets the successive_skip_count' do
- subscription
- subscription.update_columns(successive_skip_count: 3)
+RSpec.describe SolidusSubscriptions::Processor do
+ it 'schedules the processing of actionable subscriptions' do
+ actionable_subscription = create(:subscription, :actionable)
- described_class.run
+ described_class.run
- expect(subscription.reload.successive_skip_count).to eq(0)
- end
-
- context 'with clear_past_installments set to true' do
- it 'clears any past unfulfilled installments' do
- stub_config(clear_past_installments: true)
- subscription
- installment = create(:installment, :actionable, subscription: subscription)
-
- described_class.run
-
- expect(installment.reload.actionable_date).to eq(nil)
- end
- end
-
- context 'with clear_past_installments set to false' do
- it 'does not clear any past unfulfilled installments' do
- stub_config(clear_past_installments: false)
- subscription
- installment = create(:installment, :actionable, subscription: subscription)
-
- described_class.run
-
- expect(installment.reload.actionable_date).not_to be_nil
- end
- end
-
- it 'creates a new installment' do
- subscription
-
- described_class.run
-
- expect(subscription.installments.count).to eq(1)
- end
-
- it 'schedules the newly created installment for processing' do
- subscription
-
- described_class.run
-
- expect(SolidusSubscriptions::ProcessInstallmentJob).to have_been_enqueued
- .with(subscription.installments.last)
- end
-
- it 'schedules other actionable installments for processing' do
- actionable_installment = create(:installment, :actionable)
-
- described_class.run
-
- expect(SolidusSubscriptions::ProcessInstallmentJob).to have_been_enqueued
- .with(actionable_installment)
- end
+ expect(SolidusSubscriptions::ProcessSubscriptionJob).to have_been_enqueued
+ .with(actionable_subscription)
end
- shared_examples 'schedules the subscription for reprocessing' do
- it 'advances the actionable_date' do
- subscription
- subscription.update_columns(interval_length: 1, interval_units: 'week')
- old_actionable_date = subscription.actionable_date
+ it 'schedules the processing of non actionable subscriptions with actionable installments' do
+ subscription_with_actionable_installment = create(
+ :subscription,
+ actionable_date: Time.zone.today + 7.days,
+ installments: [create(:installment, :actionable)]
+ )
- described_class.run
+ described_class.run
- expect(subscription.reload.actionable_date.to_date).to eq((old_actionable_date + 1.week).to_date)
- end
+ expect(SolidusSubscriptions::ProcessSubscriptionJob).to have_been_enqueued
+ .with(subscription_with_actionable_installment)
end
- context 'with a regular subscription' do
- let(:subscription) { create(:subscription, :actionable) }
-
- include_examples 'processes the subscription'
- include_examples 'schedules the subscription for reprocessing'
- end
-
- context 'with a subscription that is pending deactivation' do
- let(:subscription) do
- create(
- :subscription,
- :actionable,
- interval_units: 'week',
- interval_length: 2,
- end_date: 3.days.from_now,
- )
- end
-
- include_examples 'processes the subscription'
- include_examples 'schedules the subscription for reprocessing'
-
- it 'deactivates the subscription' do
- subscription
-
- described_class.run
-
- expect(subscription.reload.state).to eq('inactive')
- end
- end
-
- context 'with a subscription that is pending cancellation' do
- let(:subscription) do
- create(
- :subscription,
- :actionable,
- :pending_cancellation,
- )
- end
-
- include_examples 'processes the subscription'
-
- it 'cancels the subscription' do
- subscription
+ it 'does not schedule the processing of non actionable subscriptions' do
+ non_actionable_subscription = create(:subscription, actionable_date: Time.zone.today + 14.days)
- described_class.run
+ described_class.run
- expect(subscription.reload.state).to eq('canceled')
- end
+ expect(SolidusSubscriptions::ProcessSubscriptionJob).not_to have_been_enqueued
+ .with(non_actionable_subscription)
end
end