Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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