From cb5bc7f7f1cf2493b415afc5e6be006decaade21 Mon Sep 17 00:00:00 2001 From: Matt Jonas Date: Mon, 25 Mar 2024 23:59:54 -0700 Subject: [PATCH] Fix dual call issue with interactors with deferred after_performs If an Interactor that has deferred after_performs is called from an Organizer and then...later...directly, the after_performs are skipped when calling directly. This is due to how Organizers handle Interactors with deferred logic. In such a case, the Organizer modifies the Interactor's callback so that it is skipped. However, this does not account for cases where an Interactor is called both as part of an Organizer and on it's own. In this case, the Interactor believes it should skip handling the after_performs forever, and rely instead on the Organizer to call them at the appropriate time. This change updates the logic so that the callback is only skipped if the organizer property is not present. This property is used to tell the Interactor that and by what it is being "organized". --- .../organizer/interactor_interface.rb | 2 +- ...tor_with_after_callbacks_deferred_spec.rb} | 56 ++++++++++++++++++- 2 files changed, 56 insertions(+), 2 deletions(-) rename spec/integration/{an_organizer_with_after_callbacks_deferred_spec.rb => an_organizer_with_interactor_with_after_callbacks_deferred_spec.rb} (62%) diff --git a/lib/active_interactor/organizer/interactor_interface.rb b/lib/active_interactor/organizer/interactor_interface.rb index 34ced30..d3d555e 100644 --- a/lib/active_interactor/organizer/interactor_interface.rb +++ b/lib/active_interactor/organizer/interactor_interface.rb @@ -121,7 +121,7 @@ def skip_deferred_after_perform_callbacks return unless deferred_after_perform_callbacks.present? deferred_after_perform_callbacks.each do |callback| - interactor_class.skip_callback(:perform, :after, callback.filter, raise: false) + interactor_class.skip_callback(:perform, :after, callback.filter, raise: false, if: -> { self.options.organizer.present? }) end end diff --git a/spec/integration/an_organizer_with_after_callbacks_deferred_spec.rb b/spec/integration/an_organizer_with_interactor_with_after_callbacks_deferred_spec.rb similarity index 62% rename from spec/integration/an_organizer_with_after_callbacks_deferred_spec.rb rename to spec/integration/an_organizer_with_interactor_with_after_callbacks_deferred_spec.rb index e8002c3..2ceb365 100644 --- a/spec/integration/an_organizer_with_after_callbacks_deferred_spec.rb +++ b/spec/integration/an_organizer_with_interactor_with_after_callbacks_deferred_spec.rb @@ -9,6 +9,7 @@ after_perform do context.steps << 'after_perform_1a' + context.called_test_after_perform = true end after_perform do @@ -16,6 +17,7 @@ end def perform + context.fail! if context.should_fail context.steps = [] context.steps << 'perform_1' end @@ -57,6 +59,8 @@ def perform organize TestInteractor1, TestInteractor2, TestInteractor3 end end + + let(:organizer) { interactor_class } include_examples 'a class with interactor methods' include_examples 'a class with interactor callback methods' @@ -147,5 +151,55 @@ def perform ] )} end + + context 'when interactor is called via organizer' do + context 'and interactor is called individually prior' do + it 'calls the after_perform callbacks in both cases' do + result = test_interactor_1.perform + expect(result).to be_success + expect(result.called_test_after_perform).to be(true) + + result = organizer.perform + expect(result).to be_success + expect(result.called_test_after_perform).to be(true) + end + + context 'and interactor fails when called individually' do + it 'calls the after_perform callbacks just when organized' do + result = test_interactor_1.perform(should_fail: true) + expect(result).to be_failure + expect(result.called_test_after_perform).to be_nil + + result = organizer.perform + expect(result).to be_success + expect(result.called_test_after_perform).to be(true) + end + end + end + + context 'and interactor is called individually after' do + it 'calls the after_perform callbacks in both cases' do + result = organizer.perform + expect(result).to be_success + expect(result.called_test_after_perform).to be(true) + + result = test_interactor_1.perform + expect(result).to be_success + expect(result.called_test_after_perform).to be(true) + end + + context 'and interactor fails when called individually' do + it 'calls the after_perform callbacks just when organized' do + result = organizer.perform + expect(result).to be_success + expect(result.called_test_after_perform).to be(true) + + result = test_interactor_1.perform(should_fail: true) + expect(result).to be_failure + expect(result.called_test_after_perform).to be_nil + end + end + end + end end -end \ No newline at end of file +end