From 3cf18bbb0990b8ac6cb66c90bd9377b24181aaa5 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Thu, 20 Nov 2025 18:42:09 +0100 Subject: [PATCH] feat: Make state change tracker a configurable class Currently we always enqueues a ActiveJob, but some stores maybe want to inline the state changes or add more information to the state change record. This allows to set your own state change tracker class. It defaults to the current behavior. --- .../concerns/spree/state_change_tracking.rb | 10 +++--- core/app/models/spree/state_change_tracker.rb | 31 +++++++++++++++++++ core/lib/spree/app_configuration.rb | 7 +++++ core/spec/lib/spree/app_configuration_spec.rb | 4 +++ core/spec/models/spree/order_spec.rb | 6 ++-- core/spec/models/spree/payment_spec.rb | 3 +- core/spec/models/spree/shipment_spec.rb | 3 +- .../models/spree/state_change_tracker_spec.rb | 28 +++++++++++++++++ 8 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 core/app/models/spree/state_change_tracker.rb create mode 100644 core/spec/models/spree/state_change_tracker_spec.rb diff --git a/core/app/models/concerns/spree/state_change_tracking.rb b/core/app/models/concerns/spree/state_change_tracking.rb index 0ce2dcf7a09..ac2b37b84e5 100644 --- a/core/app/models/concerns/spree/state_change_tracking.rb +++ b/core/app/models/concerns/spree/state_change_tracking.rb @@ -19,11 +19,11 @@ def enqueue_state_change_tracking previous_state, current_state = saved_changes['state'] # Enqueue the job to track this state change - StateChangeTrackingJob.perform_later( - self, - previous_state, - current_state, - Time.current + Spree::Config.state_change_tracking_class.call( + stateful: self, + previous_state:, + current_state:, + transition_timestamp: Time.current ) end end diff --git a/core/app/models/spree/state_change_tracker.rb b/core/app/models/spree/state_change_tracker.rb new file mode 100644 index 00000000000..8baec40fb77 --- /dev/null +++ b/core/app/models/spree/state_change_tracker.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Spree + # Configurable class to enqueue state change tracking jobs + # Configure your custom logic by setting Spree::Config.state_change_tracking_class + # @example Spree::Config.state_change_tracking_class = MyCustomTracker + class StateChangeTracker + # @param stateful [Object] The stateful object to track changes for + # @param previous_state [String] The previous state of the order + # @param current_state [String] The current state of the order + # @param transition_timestamp [Time] When the state transition occurred + # @param stateful_name [String] The element name of the state transition being + # tracked. It defaults to the `stateful` model element name. + def self.call( + stateful:, + previous_state:, + current_state:, + transition_timestamp:, + stateful_name: stateful.class.model_name.element + ) + # Enqueue a background job to track this state change + StateChangeTrackingJob.perform_later( + stateful, + previous_state, + current_state, + transition_timestamp, + stateful_name + ) + end + end +end diff --git a/core/lib/spree/app_configuration.rb b/core/lib/spree/app_configuration.rb index 13b1492548b..b10c4c82c4f 100644 --- a/core/lib/spree/app_configuration.rb +++ b/core/lib/spree/app_configuration.rb @@ -549,6 +549,13 @@ def payment_canceller product: '680x680>', large: '1200x1200>' } + # Allows to provide your own class for tracking state changes of stateful models + # + # @!attribute [rw] state_change_tracking_class + # @return [Class] a class with the same public interfaces as + # Spree::StateChangeTracker. + class_name_attribute :state_change_tracking_class, default: "Spree::StateChangeTracker" + # Allows providing your own class for prioritizing store credit application # to an order. # diff --git a/core/spec/lib/spree/app_configuration_spec.rb b/core/spec/lib/spree/app_configuration_spec.rb index 67f5220773c..64d37c4b133 100644 --- a/core/spec/lib/spree/app_configuration_spec.rb +++ b/core/spec/lib/spree/app_configuration_spec.rb @@ -44,6 +44,10 @@ expect(prefs.variant_price_selector_class).to eq Spree::Variant::PriceSelector end + it "uses state change tracker class by default" do + expect(prefs.state_change_tracking_class).to eq Spree::StateChangeTracker + end + it "uses core's promotion configuration class by default" do expect(prefs.promotions).to be_a Spree::Core::NullPromotionConfiguration end diff --git a/core/spec/models/spree/order_spec.rb b/core/spec/models/spree/order_spec.rb index 539d0d1edd1..d75898785ca 100644 --- a/core/spec/models/spree/order_spec.rb +++ b/core/spec/models/spree/order_spec.rb @@ -2181,7 +2181,8 @@ def validate(line_item) order, 'cart', 'address', - kind_of(Time) + kind_of(Time), + 'order' ) end @@ -2201,7 +2202,8 @@ def validate(line_item) order, 'cart', 'address', - kind_of(Time) + kind_of(Time), + 'order' ) end end diff --git a/core/spec/models/spree/payment_spec.rb b/core/spec/models/spree/payment_spec.rb index a6ed9c8c20a..2715c156512 100644 --- a/core/spec/models/spree/payment_spec.rb +++ b/core/spec/models/spree/payment_spec.rb @@ -1350,7 +1350,8 @@ payment, 'checkout', 'completed', - kind_of(Time) + kind_of(Time), + 'payment' ) end diff --git a/core/spec/models/spree/shipment_spec.rb b/core/spec/models/spree/shipment_spec.rb index 5ab22be47ca..485a73e4c21 100644 --- a/core/spec/models/spree/shipment_spec.rb +++ b/core/spec/models/spree/shipment_spec.rb @@ -974,7 +974,8 @@ shipment, 'pending', 'shipped', - kind_of(Time) + kind_of(Time), + 'shipment' ) end diff --git a/core/spec/models/spree/state_change_tracker_spec.rb b/core/spec/models/spree/state_change_tracker_spec.rb new file mode 100644 index 00000000000..246b3e3a71c --- /dev/null +++ b/core/spec/models/spree/state_change_tracker_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Spree::StateChangeTracker, type: :model do + let(:order) { create(:order) } + let(:transition_timestamp) { Time.current } + + describe "#call" do + it "enqueues a StateChangeTrackingJob with correct arguments" do + expect { + described_class.call( + stateful: order, + previous_state: "cart", + current_state: "address", + transition_timestamp: transition_timestamp, + stateful_name: "order" + ) + }.to have_enqueued_job(Spree::StateChangeTrackingJob).with( + order, + "cart", + "address", + transition_timestamp, + "order" + ) + end + end +end