Skip to content

Commit

Permalink
Merge pull request #26 from B-CDD/change/rename-subject-to-source
Browse files Browse the repository at this point in the history
Change/Rename result subject term/concept to result source
  • Loading branch information
serradura authored Jan 2, 2024
2 parents b0ae8f2 + 41ed4a9 commit 6817fe8
Show file tree
Hide file tree
Showing 142 changed files with 328 additions and 310 deletions.
31 changes: 24 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
- [\[Unreleased\]](#unreleased)
- [Changed](#changed)
- [\[0.11.0\] - 2024-01-02](#0110---2024-01-02)
- [Added](#added)
- [Changed](#changed)
- [Changed](#changed-1)
- [\[0.10.0\] - 2023-12-31](#0100---2023-12-31)
- [Added](#added-1)
- [\[0.9.1\] - 2023-12-12](#091---2023-12-12)
- [Changed](#changed-1)
- [Changed](#changed-2)
- [Fixed](#fixed)
- [\[0.9.0\] - 2023-12-12](#090---2023-12-12)
- [Added](#added-2)
- [Changed](#changed-2)
- [Changed](#changed-3)
- [\[0.8.0\] - 2023-12-11](#080---2023-12-11)
- [Added](#added-3)
- [Changed](#changed-3)
- [Changed](#changed-4)
- [Removed](#removed)
- [\[0.7.0\] - 2023-10-27](#070---2023-10-27)
- [Added](#added-4)
- [Changed](#changed-4)
- [Changed](#changed-5)
- [\[0.6.0\] - 2023-10-11](#060---2023-10-11)
- [Added](#added-5)
- [Changed](#changed-5)
- [Changed](#changed-6)
- [\[0.5.0\] - 2023-10-09](#050---2023-10-09)
- [Added](#added-6)
- [\[0.4.0\] - 2023-09-28](#040---2023-09-28)
- [Added](#added-7)
- [Changed](#changed-6)
- [Changed](#changed-7)
- [Removed](#removed-1)
- [\[0.3.0\] - 2023-09-26](#030---2023-09-26)
- [Added](#added-8)
Expand All @@ -36,6 +37,14 @@

## [Unreleased]

### Changed

- **(BREAKING)** Renames the subject concept/term to `source`. When a mixin is included/extended, it defines the `Success()` and `Failure()` methods. Since the results are generated in a context (instance or singleton where the mixin was used), they will have a defined source (instance or singleton itself).
> Definition of source
>
> From dictionary:
> * a place, person, or thing from which something comes or can be obtained.
## [0.11.0] - 2024-01-02

### Added
Expand All @@ -45,6 +54,14 @@
### Changed

- **(BREAKING)** Rename halted concept to terminal. Failures are terminal by default, but you can make a success terminal by enabling the `:continue` addon.
> Definition of terminal
>
> From dictionary:
> * of, forming, or situated at the end or extremity of something.
> * the end of a railroad or other transport route, or a station at such a point.
>
> From Wikipedia:
> * A "terminus" or "terminal" is a station at the end of a railway line.
- **(BREAKING)** Rename `BCDD::Result::Context::Success#and_expose` halted keyword argument to `terminal`.

Expand Down
35 changes: 18 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -649,9 +649,9 @@ Divide.call(2, 2)

This method generates a module that any object can include or extend. It adds two methods to the target object: `Success()` and `Failure()`.

The main difference between these methods and `BCDD::Result::Success()`/`BCDD::Result::Failure()` is that the former will utilize the target object (which has received the include/extend) as the result's subject.
The main difference between these methods and `BCDD::Result::Success()`/`BCDD::Result::Failure()` is that the former will utilize the target object (which has received the include/extend) as the result's source.

Because the result has a subject, the `#and_then` method can call methods from it.
Because the result has a source, the `#and_then` method can call methods from it.

##### Class example (Instance Methods)

Expand Down Expand Up @@ -746,9 +746,9 @@ Divide.call(4, '2') #<BCDD::Result::Failure type=:invalid_arg value="arg2 must b

To use the `#and_then` method to call methods, they must use `Success()` and `Failure()` to produce the results.

If you try to use `BCDD::Result::Subject()`/`BCDD::Result::Failure()`, or results from another `BCDD::Result.mixin` instance with `#and_then`, it will raise an error because the subjects will be different.
If you try to use `BCDD::Result::Success()`/`BCDD::Result::Failure()`, or results from another `BCDD::Result.mixin` instance with `#and_then`, it will raise an error because the sources are different.

**Note:** You can still use the block syntax, but all the results must be produced by the subject's `Success()` and `Failure()` methods.
**Note:** You can still use the block syntax, but all the results must be produced by the source's `Success()` and `Failure()` methods.

```ruby
module ValidateNonzero
Expand Down Expand Up @@ -794,13 +794,13 @@ Look at the error produced by the code above:
```ruby
Divide.call(2, 0)

# You cannot call #and_then and return a result that does not belong to the subject! (BCDD::Result::Error::InvalidResultSubject)
# Expected subject: Divide
# Given subject: ValidateNonzero
# You cannot call #and_then and return a result that does not belong to the same source! (BCDD::Result::Error::InvalidResultSource)
# Expected source: Divide
# Given source: ValidateNonzero
# Given result: #<BCDD::Result::Failure type=:division_by_zero value="arg2 must not be zero">
```

In order to fix this, you must handle the result produced by `ValidateNonzero.call()` and return a result that belongs to the subject.
In order to fix this, you must handle the result produced by `ValidateNonzero.call()` and return a result that belongs to the same source.

```ruby
module ValidateNonzero
Expand Down Expand Up @@ -832,7 +832,8 @@ module Divide
end

def validate_nonzero(numbers)
# In this case we are handling the other subject result and returning our own
# In this case we are handling the result from other source
# and returning our own
ValidateNonzero.call(numbers).handle do |on|
on.success { |numbers| Success(:ok, numbers) }

Expand All @@ -858,8 +859,8 @@ Divide.call(2, 0)

##### Dependency Injection

The `BCDD::Result#and_then` accepts a second argument that will be used to share a value with the subject's method.
To receive this argument, the subject's method must have an arity of two, where the first argument will be the result value and the second will be the shared value.
The `BCDD::Result#and_then` accepts a second argument that will be used to share a value with the source's method.
To receive this argument, the source's method must have an arity of two, where the first argument will be the result value and the second will be the injected value.

```ruby
require 'logger'
Expand Down Expand Up @@ -1863,23 +1864,23 @@ result.transitions
:parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
:current=>{:id=>1, :name=>"Division", :desc=>"divide two numbers"},
:result=>{:kind=>:success, :type=>:continued, :value=>[20, 2]},
:and_then=>{:type=>:method, :arg=>nil, :subject=><Division:0x0000000106099028>, :method_name=>:require_numbers},
:and_then=>{:type=>:method, :arg=>nil, :source=><Division:0x0000000106099028>, :method_name=>:require_numbers},
:time=>2024-01-02 03:35:11.248558 UTC
},
{
:root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
:parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
:current=>{:id=>1, :name=>"Division", :desc=>"divide two numbers"},
:result=>{:kind=>:success, :type=>:continued, :value=>[20, 2]},
:and_then=>{:type=>:method, :arg=>nil, :subject=><Division:0x0000000106099028>, :method_name=>:check_for_zeros},
:and_then=>{:type=>:method, :arg=>nil, :source=><Division:0x0000000106099028>, :method_name=>:check_for_zeros},
:time=>2024-01-02 03:35:11.248587 UTC
},
{
:root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
:parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
:current=>{:id=>1, :name=>"Division", :desc=>"divide two numbers"},
:result=>{:kind=>:success, :type=>:division_completed, :value=>10},
:and_then=>{:type=>:method, :arg=>nil, :subject=><Division:0x0000000106099028>, :method_name=>:divide},
:and_then=>{:type=>:method, :arg=>nil, :source=><Division:0x0000000106099028>, :method_name=>:divide},
:time=>2024-01-02 03:35:11.248607 UTC
},
{
Expand All @@ -1895,23 +1896,23 @@ result.transitions
:parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
:current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"},
:result=>{:kind=>:success, :type=>:continued, :value=>[10, 2]},
:and_then=>{:type=>:method, :arg=>nil, :subject=><Division:0x0000000106097ed0>, :method_name=>:require_numbers},
:and_then=>{:type=>:method, :arg=>nil, :source=><Division:0x0000000106097ed0>, :method_name=>:require_numbers},
:time=>2024-01-02 03:35:11.248661 UTC
},
{
:root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
:parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
:current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"},
:result=>{:kind=>:success, :type=>:continued, :value=>[10, 2]},
:and_then=>{:type=>:method, :arg=>nil, :subject=><Division:0x0000000106097ed0>, :method_name=>:check_for_zeros},
:and_then=>{:type=>:method, :arg=>nil, :source=><Division:0x0000000106097ed0>, :method_name=>:check_for_zeros},
:time=>2024-01-02 03:35:11.248672 UTC
},
{
:root=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
:parent=>{:id=>0, :name=>"SumDivisionsByTwo", :desc=>nil},
:current=>{:id=>2, :name=>"Division", :desc=>"divide two numbers"},
:result=>{:kind=>:success, :type=>:division_completed, :value=>5},
:and_then=>{:type=>:method, :arg=>nil, :subject=><Division:0x0000000106097ed0>, :method_name=>:divide},
:and_then=>{:type=>:method, :arg=>nil, :source=><Division:0x0000000106097ed0>, :method_name=>:divide},
:time=>2024-01-02 03:35:11.248682 UTC
},
{
Expand Down
34 changes: 17 additions & 17 deletions lib/bcdd/result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
class BCDD::Result
attr_accessor :unknown, :transitions

attr_reader :subject, :data, :type_checker, :terminal
attr_reader :source, :data, :type_checker, :terminal

protected :subject
protected :source

private :unknown, :unknown=, :type_checker, :transitions=

Expand All @@ -32,11 +32,11 @@ def self.configuration
config.freeze
end

def initialize(type:, value:, subject: nil, expectations: nil, terminal: nil)
def initialize(type:, value:, source: nil, expectations: nil, terminal: nil)
data = Data.new(kind, type, value)

@type_checker = Contract.evaluate(data, expectations)
@subject = subject
@source = source
@terminal = terminal || kind == :failure
@data = data

Expand Down Expand Up @@ -93,7 +93,7 @@ def and_then(method_name = nil, context = nil, &block)

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

method_name ? call_and_then_subject_method(method_name, context) : call_and_then_block(block)
method_name ? call_and_then_source_method(method_name, context) : call_and_then_block(block)
end

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

def call_and_then_subject_method(method_name, context_data)
method = subject.method(method_name)
def call_and_then_source_method(method_name, injected_value)
method = source.method(method_name)

Transitions.tracking.record_and_then(method, context_data, subject) do
result = call_and_then_subject_method!(method, context_data)
Transitions.tracking.record_and_then(method, injected_value, source) do
result = call_and_then_source_method!(method, injected_value)

ensure_result_object(result, origin: :method)
end
end

def call_and_then_subject_method!(method, context_data)
def call_and_then_source_method!(method, injected_value)
case method.arity
when 0 then subject.send(method.name)
when 1 then subject.send(method.name, value)
when 2 then subject.send(method.name, value, context_data)
else raise Error::InvalidSubjectMethodArity.build(subject: subject, method: method, max_arity: 2)
when 0 then source.send(method.name)
when 1 then source.send(method.name, value)
when 2 then source.send(method.name, value, injected_value)
else raise Error::InvalidSourceMethodArity.build(source: source, method: method, max_arity: 2)
end
end

def call_and_then_block(block)
Transitions.tracking.record_and_then(:block, nil, subject) do
Transitions.tracking.record_and_then(:block, nil, source) do
result = call_and_then_block!(block)

ensure_result_object(result, origin: :block)
Expand All @@ -173,8 +173,8 @@ def call_and_then_block!(block)
def ensure_result_object(result, origin:)
raise Error::UnexpectedOutcome.build(outcome: result, origin: origin) unless result.is_a?(::BCDD::Result)

return result if result.subject.equal?(subject)
return result if result.source.equal?(source)

raise Error::InvalidResultSubject.build(given_result: result, expected_subject: subject)
raise Error::InvalidResultSource.build(given_result: result, expected_source: source)
end
end
26 changes: 13 additions & 13 deletions lib/bcdd/result/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ def self.Failure(type, **value)
Failure.new(type: type, value: value)
end

def initialize(type:, value:, subject: nil, expectations: nil, terminal: nil)
def initialize(type:, value:, source: nil, expectations: nil, terminal: nil)
value.is_a?(::Hash) or raise ::ArgumentError, 'value must be a Hash'

@acc = {}

super
end

def and_then(method_name = nil, **context_data, &block)
super(method_name, context_data, &block)
def and_then(method_name = nil, **injected_value, &block)
super(method_name, injected_value, &block)
end

protected
Expand All @@ -33,20 +33,20 @@ def and_then(method_name = nil, **context_data, &block)

private

SubjectMethodArity = ->(method) do
SourceMethodArity = ->(method) do
return 0 if method.arity.zero?
return 1 if method.parameters.map(&:first).all?(/\Akey/)

-1
end

def call_and_then_subject_method!(method, context_data)
acc.merge!(value.merge(context_data))
def call_and_then_source_method!(method, injected_value)
acc.merge!(value.merge(injected_value))

case SubjectMethodArity[method]
when 0 then subject.send(method.name)
when 1 then subject.send(method.name, **acc)
else raise Error::InvalidSubjectMethodArity.build(subject: subject, method: method, max_arity: 1)
case SourceMethodArity[method]
when 0 then source.send(method.name)
when 1 then source.send(method.name, **acc)
else raise Error::InvalidSourceMethodArity.build(source: source, method: method, max_arity: 1)
end
end

Expand All @@ -59,9 +59,9 @@ def call_and_then_block!(block)
def ensure_result_object(result, origin:)
raise_unexpected_outcome_error(result, origin) unless result.is_a?(Context)

return result.tap { _1.acc.merge!(acc) } if result.subject.equal?(subject)
return result.tap { _1.acc.merge!(acc) } if result.source.equal?(source)

raise Error::InvalidResultSubject.build(given_result: result, expected_subject: subject)
raise Error::InvalidResultSource.build(given_result: result, expected_source: source)
end

EXPECTED_OUTCOME = 'BCDD::Result::Context::Success or BCDD::Result::Context::Failure'
Expand All @@ -70,6 +70,6 @@ def raise_unexpected_outcome_error(result, origin)
raise Error::UnexpectedOutcome.build(outcome: result, origin: origin, expected: EXPECTED_OUTCOME)
end

private_constant :SubjectMethodArity, :EXPECTED_OUTCOME
private_constant :SourceMethodArity, :EXPECTED_OUTCOME
end
end
4 changes: 2 additions & 2 deletions lib/bcdd/result/context/expectations/mixin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ module Expectations::Mixin
module Addons
module Continue
private def Continue(**value)
Success.new(type: :continued, value: value, subject: self)
Success.new(type: :continued, value: value, source: self)
end
end

module Given
private def Given(*values)
value = values.map(&:to_h).reduce({}) { |acc, val| acc.merge(val) }

Success.new(type: :given, value: value, subject: self)
Success.new(type: :given, value: value, source: self)
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/bcdd/result/context/mixin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def Failure(type, **value)
end

private def _ResultAs(kind_class, type, value, terminal: nil)
kind_class.new(type: type, value: value, subject: self, terminal: terminal)
kind_class.new(type: type, value: value, source: self, terminal: terminal)
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/bcdd/result/context/success.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ def and_expose(type, keys, terminal: true)

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

self.class.new(type: type, value: exposed_value, subject: subject, terminal: terminal)
self.class.new(type: type, value: exposed_value, source: source, terminal: terminal)
end
end
Loading

0 comments on commit 6817fe8

Please sign in to comment.