From 5d22f691fe289f4ac9c6c6fc4e9e534d68f6bf5e Mon Sep 17 00:00:00 2001 From: Daniel Nolan Date: Fri, 20 Jan 2023 13:50:39 -0500 Subject: [PATCH] Fix ClassInvoker instantiating class twice * I'm in the process of updating a rails app from rails 5.2 to rails 7. Currently the app is using AASM version 4.12.3. When I update to AASM version 5.x we start getting a failing spec in our app. The implementation uses the `after:` dsl and passes it a class. ``` ruby event :run_payment_create do transitions to: :active, after: Payment::GoCardless::Subscription::Charge end ``` The spec in our app expects that class to receive new 1 time with the args passed from after, but the spec is failing saying that `Payment::GoCardless::Subscription::Charge` received `.new` with the expected args twice. I started debugging with `bundle open aasm` and pry and I found that the `ClassInvoker` has a `instance` method that memoizes the instance returned from the `retrieve_instance` method, but the `instance` method was only being used by `log_source_location` and `log_method_info` methods while `invoke_subject` was calling `retrieve_instance` directly resulting in `retrieve_instance` being called twice. * Update `invoke_subject` method to use `instance.call` so that the instance will be memoized and subsequent calls to `instance` won't try to instantiate the class a second time. --- lib/aasm/core/invokers/class_invoker.rb | 2 +- spec/unit/invokers/class_invoker_spec.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/aasm/core/invokers/class_invoker.rb b/lib/aasm/core/invokers/class_invoker.rb index 2f16840..4491052 100644 --- a/lib/aasm/core/invokers/class_invoker.rb +++ b/lib/aasm/core/invokers/class_invoker.rb @@ -17,7 +17,7 @@ module AASM end def invoke_subject - @result = retrieve_instance.call + @result = instance.call end private diff --git a/spec/unit/invokers/class_invoker_spec.rb b/spec/unit/invokers/class_invoker_spec.rb index 63a93e4..7e4f0f0 100644 --- a/spec/unit/invokers/class_invoker_spec.rb +++ b/spec/unit/invokers/class_invoker_spec.rb @@ -52,6 +52,18 @@ describe AASM::Core::Invokers::ClassInvoker do expect(subject.failures.first).to be_a(Method) end + + context 'when a failure occurs' do + let(:target) { Class.new { def initialize(_r, *_a); end; def call; end } } + + it 'only instantiates subject class one time' do + target_instance = target.new(record, *args) + + expect(target).to receive(:new).with(record, *args).and_return(target_instance).once + + expect { subject.invoke }.not_to raise_error + end + end end end