Skip to content

Commit

Permalink
Merge pull request #20 from Kaligo/feature/result-monad-inherited-cal…
Browse files Browse the repository at this point in the history
…lbacks

Make ResultMonad callbacks inherited
  • Loading branch information
Drenmi authored Nov 12, 2021
2 parents 7d59360 + 82843f8 commit 3b77725
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 12 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 0.9.0

### New features

- `ResultMonad` callbacks (`before_success`, `before_error`) are now inherited.

## 0.8.3

### New features
Expand Down
35 changes: 33 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:**

Expand All @@ -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
Expand Down
6 changes: 4 additions & 2 deletions lib/stimpack/result_monad.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion lib/stimpack/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Stimpack
VERSION = "0.8.3"
VERSION = "0.9.0"
end
38 changes: 31 additions & 7 deletions spec/stimpack/result_monad_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -17,6 +21,8 @@ def error_result(errors:)
error(errors: errors)
end

def accumulator; end

def self.to_s
"Foo"
end
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 3b77725

Please sign in to comment.