Skip to content

Commit

Permalink
Merge pull request #20 from B-CDD/feature/and_expose_must_halt_by_def…
Browse files Browse the repository at this point in the history
…ault

Make `BCDD::Result::Context::Success#and_expose()` to produce a halted success by default
  • Loading branch information
serradura authored Dec 12, 2023
2 parents 40d98d8 + bb3e942 commit 913eeca
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 21 deletions.
20 changes: 15 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
- [\[Unreleased\]](#unreleased)
- [Changed](#changed)
- [Fixed](#fixed)
- [\[0.9.0\] - 2023-12-12](#090---2023-12-12)
- [Added](#added)
- [Changed](#changed)
- [Changed](#changed-1)
- [\[0.8.0\] - 2023-12-11](#080---2023-12-11)
- [Added](#added-1)
- [Changed](#changed-1)
- [Changed](#changed-2)
- [Removed](#removed)
- [\[0.7.0\] - 2023-10-27](#070---2023-10-27)
- [Added](#added-2)
- [Changed](#changed-2)
- [Changed](#changed-3)
- [\[0.6.0\] - 2023-10-11](#060---2023-10-11)
- [Added](#added-3)
- [Changed](#changed-3)
- [Changed](#changed-4)
- [\[0.5.0\] - 2023-10-09](#050---2023-10-09)
- [Added](#added-4)
- [\[0.4.0\] - 2023-09-28](#040---2023-09-28)
- [Added](#added-5)
- [Changed](#changed-4)
- [Changed](#changed-5)
- [Removed](#removed-1)
- [\[0.3.0\] - 2023-09-26](#030---2023-09-26)
- [Added](#added-6)
Expand All @@ -28,6 +30,14 @@

## [Unreleased]

### Changed

- **(BREAKING)** Make `BCDD::Result::Context::Success#and_expose()` to produce a halted success by default. You can turn this off by passing `halted: false`.

### Fixed

- Make `BCDD::Result::Context#and_then(&block)` accumulate the result value.

## [0.9.0] - 2023-12-12

### Added
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1569,6 +1569,8 @@ Divide.new.call(10, 5)
#<BCDD::Result::Context::Success type=:ok value={:number=>2, :number1=>10, :number2=>5}>
```

> PS: The `#and_expose` produces a halted success by default. This means the next step will not be executed even if you call `#and_then` after `#and_expose`. To change this behavior, you can pass `halted: false` to `#and_expose`.
<p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>

##### Module example (Singleton Methods)
Expand Down
22 changes: 14 additions & 8 deletions lib/bcdd/result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,12 @@ def on_unknown
tap { yield(value, type) if unknown }
end

def and_then(method_name = nil, context = nil)
def and_then(method_name = nil, context = nil, &block)
return self if halted?

method_name && block_given? and raise ::ArgumentError, 'method_name and block are mutually exclusive'
method_name && block and raise ::ArgumentError, 'method_name and block are mutually exclusive'

return call_subject_method(method_name, context) if method_name

result = yield(value)

ensure_result_object(result, origin: :block)
method_name ? call_and_then_subject_method(method_name, context) : call_and_then_block(block)
end

def handle
Expand Down Expand Up @@ -139,7 +135,7 @@ def known(block)
block.call(value, type)
end

def call_subject_method(method_name, context)
def call_and_then_subject_method(method_name, context)
method = subject.method(method_name)

result =
Expand All @@ -153,6 +149,16 @@ def call_subject_method(method_name, context)
ensure_result_object(result, origin: :method)
end

def call_and_then_block(block)
call_and_then_block!(block, value)
end

def call_and_then_block!(block, value)
result = block.call(value)

ensure_result_object(result, origin: :block)
end

def ensure_result_object(result, origin:)
raise Error::UnexpectedOutcome.build(outcome: result, origin: origin) unless result.is_a?(::BCDD::Result)

Expand Down
8 changes: 7 additions & 1 deletion lib/bcdd/result/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def and_then(method_name = nil, **context_data, &block)
-1
end

def call_subject_method(method_name, context)
def call_and_then_subject_method(method_name, context)
method = subject.method(method_name)

acc.merge!(value.merge(context))
Expand All @@ -55,6 +55,12 @@ def call_subject_method(method_name, context)
ensure_result_object(result, origin: :method)
end

def call_and_then_block(block)
acc.merge!(value)

call_and_then_block!(block, acc)
end

def ensure_result_object(result, origin:)
raise_unexpected_outcome_error(result, origin) unless result.is_a?(Context)

Expand Down
2 changes: 1 addition & 1 deletion lib/bcdd/result/context/failure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
class BCDD::Result::Context::Failure < BCDD::Result::Context
include BCDD::Result::Failure::Methods

def and_expose(_type, _keys)
def and_expose(_type, _keys, **_options)
self
end
end
4 changes: 2 additions & 2 deletions lib/bcdd/result/context/success.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
class BCDD::Result::Context::Success < BCDD::Result::Context
include ::BCDD::Result::Success::Methods

def and_expose(type, keys)
def and_expose(type, keys, halted: true)
unless keys.is_a?(::Array) && !keys.empty? && keys.all?(::Symbol)
raise ::ArgumentError, 'keys must be an Array of Symbols'
end

exposed_value = acc.merge(value).slice(*keys)

self.class.new(type: type, value: exposed_value, subject: subject)
self.class.new(type: type, value: exposed_value, subject: subject, halted: halted)
end
end
10 changes: 6 additions & 4 deletions sig/bcdd/result.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ class BCDD::Result

def kind: -> Symbol
def known: (Proc) -> untyped
def call_subject_method: (Symbol, untyped) -> BCDD::Result
def call_and_then_subject_method: (Symbol, untyped) -> BCDD::Result
def call_and_then_block: (untyped) -> BCDD::Result
def call_and_then_block!: (untyped, untyped) -> BCDD::Result
def ensure_result_object: (untyped, origin: Symbol) -> BCDD::Result
end

Expand Down Expand Up @@ -442,7 +444,7 @@ class BCDD::Result::Context < BCDD::Result

private

def call_subject_method: (Symbol, Hash[Symbol, untyped]) -> BCDD::Result::Context
def call_and_then_subject_method: (Symbol, Hash[Symbol, untyped]) -> BCDD::Result::Context
def ensure_result_object: (untyped, origin: Symbol) -> BCDD::Result::Context

def raise_unexpected_outcome_error: (BCDD::Result::Context | untyped, Symbol) -> void
Expand All @@ -452,7 +454,7 @@ class BCDD::Result::Context
class Success < BCDD::Result::Context
include BCDD::Result::Success::Methods

def and_expose: (Symbol, Array[Symbol]) -> BCDD::Result::Context::Success
def and_expose: (Symbol, Array[Symbol], halted: bool) -> BCDD::Result::Context::Success
end

def self.Success: (Symbol, **untyped) -> BCDD::Result::Context::Success
Expand All @@ -462,7 +464,7 @@ class BCDD::Result::Context
class Failure < BCDD::Result::Context
include BCDD::Result::Failure::Methods

def and_expose: (Symbol, Array[Symbol]) -> BCDD::Result::Context::Failure
def and_expose: (Symbol, Array[Symbol], **untyped) -> BCDD::Result::Context::Failure
end

def self.Failure: (Symbol, **untyped) -> BCDD::Result::Context::Failure
Expand Down
85 changes: 85 additions & 0 deletions test/bcdd/result/context/and_expose/halting_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# frozen_string_literal: true

require 'test_helper'

class BCDD::Result
class ContextHaltingTest < Minitest::Test
module HaltingEnabledAndThenBlock
extend self, Context.mixin

def call
Success(:a, a: 1)
.and_then { Success(:b, b: 2) }
.and_expose(:a_and_b, %i[a b]) # the default is halted
.and_then { Success(:c, c: 3) }
end
end

module HaltingEnabledAndThenMethod
extend self, Context.mixin

def call
call_a
.and_then(:call_b)
.and_expose(:a_and_b, %i[a b]) # the default is halted
.and_then(:call_c)
end

private

def call_a; Success(:a, a: 1); end
def call_b; Success(:b, b: 2); end
def call_c; Success(:c, c: 3); end
end

module HaltingDisabledAndThenBlock
extend self, Context.mixin

def call
Success(:a, a: 1)
.and_then { Success(:b, b: 2) }
.and_expose(:a_and_b, %i[a b], halted: false)
.and_then { Success(:c, c: 3) }
end
end

module HaltingDisabledAndThenMethod
extend self, Context.mixin

def call
call_a
.and_then(:call_b)
.and_expose(:a_and_b, %i[a b], halted: false)
.and_then(:call_c)
end

private

def call_a; Success(:a, a: 1); end
def call_b; Success(:b, b: 2); end
def call_c; Success(:c, c: 3); end
end

test 'by default, #and_expose halts the execution' do
result1 = HaltingEnabledAndThenBlock.call
result2 = HaltingEnabledAndThenMethod.call

assert result1.success?(:a_and_b)
assert_equal({ a: 1, b: 2 }, result1.value)

assert result2.success?(:a_and_b)
assert_equal({ a: 1, b: 2 }, result2.value)
end

test 'when halted is false, #and_expose does not halt the execution' do
result1 = HaltingDisabledAndThenBlock.call
result2 = HaltingDisabledAndThenMethod.call

assert result1.success?(:c)
assert_equal({ c: 3 }, result1.value)

assert result2.success?(:c)
assert_equal({ c: 3 }, result2.value)
end
end
end

0 comments on commit 913eeca

Please sign in to comment.