diff --git a/CHANGELOG.md b/CHANGELOG.md index 89b3630..9eb1938 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.9.2 + +### Bug fixes + +- Prevent duplicate invocation of the error callback when guarding an instance + method. + ## 0.9.1 ### Bug fixes diff --git a/Gemfile.lock b/Gemfile.lock index e119590..defddce 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,19 +1,19 @@ PATH remote: . specs: - stimpack (0.9.1) + stimpack (0.9.2) activesupport (>= 6.1) GEM remote: https://rubygems.org/ specs: - activesupport (7.0.2.2) + activesupport (7.0.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) ast (2.4.2) - concurrent-ruby (1.1.9) + concurrent-ruby (1.1.10) diff-lcs (1.4.4) i18n (1.10.0) concurrent-ruby (~> 1.0) diff --git a/lib/stimpack/result_monad/guard_clause.rb b/lib/stimpack/result_monad/guard_clause.rb index 247f8ad..8d77bc3 100644 --- a/lib/stimpack/result_monad/guard_clause.rb +++ b/lib/stimpack/result_monad/guard_clause.rb @@ -29,7 +29,11 @@ module GuardCatcher def call super rescue GuardFailed => e - run_callback(:error) + # Avoid running the error callback in the case where it was already + # called by using `#error` in an instance method that is guarded + # against in the `#call` method. + # + run_callback(:error) unless e.result.klass == self.class e.result end diff --git a/lib/stimpack/version.rb b/lib/stimpack/version.rb index efdac0f..9a7d868 100644 --- a/lib/stimpack/version.rb +++ b/lib/stimpack/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Stimpack - VERSION = "0.9.1" + VERSION = "0.9.2" end diff --git a/spec/stimpack/result_monad/guard_clause_spec.rb b/spec/stimpack/result_monad/guard_clause_spec.rb index 1bfeadb..1fe4908 100644 --- a/spec/stimpack/result_monad/guard_clause_spec.rb +++ b/spec/stimpack/result_monad/guard_clause_spec.rb @@ -5,7 +5,8 @@ RSpec.describe Stimpack::ResultMonad::GuardClause do subject(:service) { klass } - let(:instance) { service.new } + let(:instance) { service.new(private_error: private_error) } + let(:private_error) { false } let(:klass) do Class.new do @@ -13,6 +14,12 @@ result :foo + def initialize(private_error: false) + @private_error = private_error + end + + attr_reader :private_error + def success_result(**options) success(**options) end @@ -27,9 +34,12 @@ def self.to_s def call guard :foo + bar_result = guard { bar } baz_result = guard { baz } + guard { qux } if private_error + success(foo: bar_result + baz_result) rescue StandardError error(errors: "Something went wrong, but not a guard fail.") @@ -48,6 +58,10 @@ def bar def baz pass("Baz") end + + def qux + error(errors: ["Same class error"]) + end end end @@ -55,6 +69,7 @@ def baz double( Stimpack::ResultMonad::Result, failed?: true, + klass: "Foo", errors: ["Inner error"] ) end @@ -63,6 +78,7 @@ def baz double( Stimpack::ResultMonad::Result, failed?: false, + klass: "Foo", unwrap!: "Foo", errors: nil ) @@ -121,15 +137,33 @@ def baz end describe ".before_error" do - before do - allow(instance).to receive(:inspect) - allow(instance).to receive(:foo).and_return(inner_service_error) + context "when inner service fails" do + before do + allow(instance).to receive(:inspect) + allow(instance).to receive(:foo).and_return(inner_service_error) - service.before_error { inspect } + service.before_error { inspect } + + instance.() + end - instance.() + it { expect(instance).to have_received(:inspect).once } end - it { expect(instance).to have_received(:inspect).once } + context "when passing an error from an instance method" do + let(:private_error) { true } + + before do + allow(instance).to receive(:inspect) + allow(instance).to receive(:foo).and_return(inner_service_success) + allow(instance).to receive(:bar).and_return(inner_service_success) + + service.before_error { inspect } + + instance.() + end + + it { expect(instance).to have_received(:inspect).once } + end end end