From 82843f8c7283ee796406ba7ef7ac27145f458d3a Mon Sep 17 00:00:00 2001 From: Ted Johansson Date: Fri, 12 Nov 2021 21:56:20 +0800 Subject: [PATCH] Make ResultMonad callbacks inherited --- CHANGELOG.md | 6 +++++ README.md | 35 +++++++++++++++++++++++++-- lib/stimpack/result_monad.rb | 6 +++-- lib/stimpack/version.rb | 2 +- spec/stimpack/result_monad_spec.rb | 38 ++++++++++++++++++++++++------ 5 files changed, 75 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 614b97e..70dcce6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.9.0 + +### New features + +- `ResultMonad` callbacks (`before_success`, `before_error`) are now inherited. + ## 0.8.3 ### New features diff --git a/README.md b/README.md index 4844e65..705210b 100644 --- a/README.md +++ b/README.md @@ -291,8 +291,8 @@ The `ResultMonad` mixin exposes two callbacks, `before_success` and `before_error`. These can be configured by passing a block to them in the class body. -*Note: Callbacks are not inherited, and declaring multiple callbacks in the -same class will overwrite the previous one.* +*Note: Declaring an already declared callback in the same class will overwrite +the previous one.* **Example:** @@ -315,6 +315,37 @@ end *Note: The block is evaluated in the context of the instance, so you can call any instance methods from inside the block.* +Callbacks are inherited, and all inherited callbacks will be invoked as they +are traversed up the inheritance chain. In this case, all callbacks are +evaluated in the context of the class where the `success` or `error` method +was called. + +**Example:** + +```ruby +class Foo + include Stimpack::ResultMonad + + before_success do + puts "Parent" + end +end + +class Bar < Foo + before_success do + puts "Child" + end + + def call + success + end +end + +Bar.() +#=> "Child" +#=> "Parent" +``` + ### Guard clauses The `ResultMonad::GuardClause` mixin (included by default) allows for stepwise diff --git a/lib/stimpack/result_monad.rb b/lib/stimpack/result_monad.rb index f5a0325..5585c95 100644 --- a/lib/stimpack/result_monad.rb +++ b/lib/stimpack/result_monad.rb @@ -174,9 +174,11 @@ def incompatible_result_error(actual_attributes) end def run_callback(name) - callback = self.class.callbacks["#{self.class}.#{name}"] + self.class.ancestors.each do |ancestor| + callback = self.class.callbacks["#{ancestor}.#{name}"] - instance_exec(&callback) if callback.respond_to?(:call) + instance_exec(&callback) if callback.respond_to?(:call) + end end end end diff --git a/lib/stimpack/version.rb b/lib/stimpack/version.rb index b5241f8..3301ca1 100644 --- a/lib/stimpack/version.rb +++ b/lib/stimpack/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Stimpack - VERSION = "0.8.3" + VERSION = "0.9.0" end diff --git a/spec/stimpack/result_monad_spec.rb b/spec/stimpack/result_monad_spec.rb index e2d3843..cf988ad 100644 --- a/spec/stimpack/result_monad_spec.rb +++ b/spec/stimpack/result_monad_spec.rb @@ -5,10 +5,14 @@ RSpec.describe Stimpack::ResultMonad do subject(:service) { klass } - let(:klass) do + let(:super_klass) do Class.new do include Stimpack::ResultMonad + end + end + let(:klass) do + Class.new(super_klass) do def success_result(**options) success(**options) end @@ -17,6 +21,8 @@ def error_result(errors:) error(errors: errors) end + def accumulator; end + def self.to_s "Foo" end @@ -87,32 +93,50 @@ def self.to_s describe ".before_success" do let(:instance) { service.new } + let(:accumulator) { spy } before do - allow(instance).to receive(:inspect) + allow(instance).to receive(:accumulator).and_return(accumulator) + + allow(accumulator).to receive(:callback) + allow(accumulator).to receive(:parent_callback) service.blank_result - service.before_success { inspect } + service.before_success { accumulator.callback } + + super_klass.before_success { accumulator.parent_callback } instance.success_result end - it { expect(instance).to have_received(:inspect).once } + it "runs the callbacks up the class hierarchy" do + expect(accumulator).to have_received(:callback).ordered + expect(accumulator).to have_received(:parent_callback).ordered + end end describe ".before_error" do let(:instance) { service.new } + let(:accumulator) { spy } before do - allow(instance).to receive(:inspect) + allow(instance).to receive(:accumulator).and_return(accumulator) + + allow(accumulator).to receive(:callback) + allow(accumulator).to receive(:parent_callback) service.blank_result - service.before_error { inspect } + service.before_error { accumulator.callback } + + super_klass.before_error { accumulator.parent_callback } instance.error_result(errors: ["foo"]) end - it { expect(instance).to have_received(:inspect).once } + it "runs the callbacks up the class hierarchy" do + expect(accumulator).to have_received(:callback).ordered + expect(accumulator).to have_received(:parent_callback).ordered + end end describe "#success" do