bcdd-result 0.13.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -4
- data/CHANGELOG.md +46 -17
- data/README.md +381 -220
- data/Rakefile +1 -1
- data/Steepfile +1 -1
- data/examples/multiple_listeners/Rakefile +8 -8
- data/examples/multiple_listeners/app/models/account/owner_creation.rb +1 -1
- data/examples/multiple_listeners/app/models/user/creation.rb +1 -1
- data/examples/multiple_listeners/app/models/user/token/creation.rb +1 -1
- data/examples/multiple_listeners/config/initializers/bcdd.rb +0 -2
- data/examples/multiple_listeners/config.rb +3 -3
- data/examples/multiple_listeners/db/setup.rb +2 -3
- data/examples/multiple_listeners/lib/bcdd/result/event_logs_record.rb +27 -0
- data/examples/multiple_listeners/lib/event_logs_listener/stdout.rb +60 -0
- data/examples/multiple_listeners/lib/runtime_breaker.rb +1 -1
- data/examples/service_objects/Rakefile +36 -0
- data/examples/service_objects/app/models/account/member.rb +10 -0
- data/examples/service_objects/app/models/account.rb +11 -0
- data/examples/service_objects/app/models/user/token.rb +7 -0
- data/examples/service_objects/app/models/user.rb +15 -0
- data/examples/service_objects/app/services/account/owner_creation.rb +47 -0
- data/examples/service_objects/app/services/application_service.rb +79 -0
- data/examples/service_objects/app/services/user/creation.rb +56 -0
- data/examples/service_objects/app/services/user/token/creation.rb +37 -0
- data/examples/service_objects/config/boot.rb +17 -0
- data/examples/service_objects/config/initializers/bcdd.rb +9 -0
- data/examples/service_objects/config.rb +20 -0
- data/examples/service_objects/db/setup.rb +49 -0
- data/examples/single_listener/Rakefile +5 -5
- data/examples/single_listener/app/models/account/owner_creation.rb +1 -1
- data/examples/single_listener/app/models/user/creation.rb +1 -1
- data/examples/single_listener/app/models/user/token/creation.rb +1 -1
- data/examples/single_listener/config/initializers/bcdd.rb +0 -2
- data/examples/single_listener/config.rb +1 -1
- data/examples/single_listener/lib/{single_transitions_listener.rb → single_event_logs_listener.rb} +32 -23
- data/lib/bcdd/{result/context → context}/callable_and_then.rb +5 -4
- data/lib/bcdd/{result/context → context}/expectations/mixin.rb +1 -1
- data/lib/bcdd/{result/context → context}/expectations.rb +2 -2
- data/lib/bcdd/context/failure.rb +9 -0
- data/lib/bcdd/{result/context → context}/mixin.rb +2 -2
- data/lib/bcdd/{result/context → context}/success.rb +6 -6
- data/lib/bcdd/context.rb +91 -0
- data/lib/bcdd/failure.rb +23 -0
- data/lib/bcdd/result/_self.rb +198 -0
- data/lib/bcdd/result/callable_and_then/caller.rb +1 -1
- data/lib/bcdd/result/config/switchers/addons.rb +2 -2
- data/lib/bcdd/result/config/switchers/constant_aliases.rb +1 -3
- data/lib/bcdd/result/config/switchers/features.rb +5 -5
- data/lib/bcdd/result/config/switchers/pattern_matching.rb +1 -1
- data/lib/bcdd/result/config.rb +7 -5
- data/lib/bcdd/result/contract/type_checker.rb +4 -0
- data/lib/bcdd/result/{transitions → event_logs}/config.rb +5 -3
- data/lib/bcdd/result/{transitions → event_logs}/listener.rb +5 -5
- data/lib/bcdd/result/{transitions → event_logs}/listeners.rb +17 -17
- data/lib/bcdd/result/{transitions → event_logs}/tracking/disabled.rb +1 -1
- data/lib/bcdd/result/{transitions → event_logs}/tracking/enabled.rb +15 -13
- data/lib/bcdd/result/{transitions → event_logs}/tracking.rb +4 -3
- data/lib/bcdd/result/{transitions → event_logs}/tree.rb +27 -11
- data/lib/bcdd/result/event_logs.rb +27 -0
- data/lib/bcdd/result/failure.rb +1 -3
- data/lib/bcdd/result/success.rb +1 -3
- data/lib/bcdd/result/version.rb +1 -1
- data/lib/bcdd/result.rb +23 -191
- data/lib/bcdd/success.rb +23 -0
- data/sig/bcdd/context.rbs +175 -0
- data/sig/bcdd/failure.rbs +13 -0
- data/sig/bcdd/result/config.rbs +1 -3
- data/sig/bcdd/result/context.rbs +2 -174
- data/sig/bcdd/result/contract.rbs +1 -0
- data/sig/bcdd/result/{transitions.rbs → event_logs.rbs} +19 -19
- data/sig/bcdd/result.rbs +13 -31
- data/sig/bcdd/success.rbs +13 -0
- metadata +41 -24
- data/examples/multiple_listeners/lib/bcdd/result/transitions_record.rb +0 -28
- data/examples/multiple_listeners/lib/transitions_listener/stdout.rb +0 -54
- data/lib/bcdd/result/context/failure.rb +0 -9
- data/lib/bcdd/result/context.rb +0 -93
- data/lib/bcdd/result/failure/methods.rb +0 -21
- data/lib/bcdd/result/success/methods.rb +0 -21
- data/lib/bcdd/result/transitions.rb +0 -27
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
<h1 align="center" id="-bcddresult">🔀 BCDD::Result</h1>
|
3
3
|
<p align="center"><i>Unleash a pragmatic and observable use of Result Pattern and Railway-Oriented Programming in Ruby.</i></p>
|
4
4
|
<p align="center">
|
5
|
-
<img src="https://img.shields.io/badge/
|
5
|
+
<img src="https://img.shields.io/badge/Ruby%20%3E%3D%202.7%2C%20%3C%3D%20Head-ruby.svg?colorA=444&colorB=333" alt="Ruby">
|
6
6
|
<a href="https://rubygems.org/gems/bcdd-result"><img src="https://badge.fury.io/rb/bcdd-result.svg" alt="bcdd-result gem version" height="18"></a>
|
7
7
|
<a href="https://codeclimate.com/github/B-CDD/result/maintainability"><img src="https://api.codeclimate.com/v1/badges/aa8360f8f012d7dedd62/maintainability" /></a>
|
8
8
|
<a href="https://codeclimate.com/github/B-CDD/result/test_coverage"><img src="https://api.codeclimate.com/v1/badges/aa8360f8f012d7dedd62/test_coverage" /></a>
|
@@ -19,12 +19,13 @@ Furthermore, this abstraction exposes several features that will be useful to ma
|
|
19
19
|
|
20
20
|
Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofit.com/rop/) pattern (superpower) in your code.
|
21
21
|
|
22
|
-
- [Ruby
|
22
|
+
- [Supported Ruby](#supported-ruby)
|
23
23
|
- [Installation](#installation)
|
24
24
|
- [Usage](#usage)
|
25
25
|
- [`BCDD::Result` *versus* `Result`](#bcddresult-versus-result)
|
26
26
|
- [Reference](#reference)
|
27
|
-
- [
|
27
|
+
- [Basic methods](#basic-methods)
|
28
|
+
- [Checking types with `result.is?` or `method missing`](#checking-types-with-resultis-or-method-missing)
|
28
29
|
- [Checking types with `result.success?` or `result.failure?`](#checking-types-with-resultsuccess-or-resultfailure)
|
29
30
|
- [Result Hooks](#result-hooks)
|
30
31
|
- [`result.on`](#resulton)
|
@@ -37,9 +38,6 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
|
|
37
38
|
- [`result.value_or`](#resultvalue_or)
|
38
39
|
- [Result Data](#result-data)
|
39
40
|
- [`result.data`](#resultdata)
|
40
|
-
- [Pattern Matching](#pattern-matching)
|
41
|
-
- [`Array`/`Find` patterns](#arrayfind-patterns)
|
42
|
-
- [`Hash` patterns](#hash-patterns)
|
43
41
|
- [Railway Oriented Programming](#railway-oriented-programming)
|
44
42
|
- [`result.and_then`](#resultand_then)
|
45
43
|
- [`BCDD::Result.mixin`](#bcddresultmixin)
|
@@ -63,17 +61,25 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
|
|
63
61
|
- [Failure()](#failure)
|
64
62
|
- [Pattern Matching Support](#pattern-matching-support)
|
65
63
|
- [`BCDD::Result::Expectations.mixin` add-ons](#bcddresultexpectationsmixin-add-ons)
|
66
|
-
- [`BCDD::
|
64
|
+
- [`BCDD::Context`](#bcddcontext)
|
67
65
|
- [Defining successes and failures](#defining-successes-and-failures)
|
68
66
|
- [Constant aliases](#constant-aliases)
|
69
|
-
- [`BCDD::
|
67
|
+
- [`BCDD::Context.mixin`](#bcddcontextmixin)
|
70
68
|
- [Class example (Instance Methods)](#class-example-instance-methods-1)
|
71
69
|
- [`and_expose`](#and_expose)
|
72
70
|
- [Module example (Singleton Methods)](#module-example-singleton-methods-1)
|
73
|
-
- [`BCDD::
|
71
|
+
- [`BCDD::Context::Expectations`](#bcddcontextexpectations)
|
74
72
|
- [Mixin add-ons](#mixin-add-ons)
|
75
|
-
- [
|
76
|
-
- [`
|
73
|
+
- [Pattern Matching](#pattern-matching)
|
74
|
+
- [`BCDD::Result`](#bcddresult)
|
75
|
+
- [`Array`/`Find` patterns](#arrayfind-patterns)
|
76
|
+
- [`Hash` patterns](#hash-patterns)
|
77
|
+
- [`BCDD::Context`](#bcddcontext-1)
|
78
|
+
- [`Array`/`Find` patterns](#arrayfind-patterns-1)
|
79
|
+
- [`Hash` patterns](#hash-patterns-1)
|
80
|
+
- [How to pattern match without the concept of success and failure](#how-to-pattern-match-without-the-concept-of-success-and-failure)
|
81
|
+
- [`BCDD::Result.event_logs`](#bcddresultevent_logs)
|
82
|
+
- [`metadata: {ids:}`](#metadata-ids)
|
77
83
|
- [Configuration](#configuration)
|
78
84
|
- [Turning on/off](#turning-onoff)
|
79
85
|
- [Setting a `trace_id` fetcher](#setting-a-trace_id-fetcher)
|
@@ -93,9 +99,13 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
|
|
93
99
|
- [License](#license)
|
94
100
|
- [Code of Conduct](#code-of-conduct)
|
95
101
|
|
96
|
-
## Ruby
|
102
|
+
## Supported Ruby
|
103
|
+
|
104
|
+
This library is tested against:
|
97
105
|
|
98
|
-
|
106
|
+
Version | 2.7 | 3.0 | 3.1 | 3.2 | 3.3 | Head
|
107
|
+
---- | --- | --- | --- | --- | --- | ---
|
108
|
+
100% Coverage | ✅ | ✅ | ✅ | ✅ | ✅ | ✅
|
99
109
|
|
100
110
|
## Installation
|
101
111
|
|
@@ -167,7 +177,7 @@ There are other aliases and configurations available. Check the [BCDD::Result.co
|
|
167
177
|
|
168
178
|
## Reference
|
169
179
|
|
170
|
-
###
|
180
|
+
### Basic methods
|
171
181
|
|
172
182
|
Both `BCDD::Result::Success` and `BCDD::Result::Failure` are composed of the same methods. Look at the basic ones:
|
173
183
|
|
@@ -179,20 +189,22 @@ Both `BCDD::Result::Success` and `BCDD::Result::Failure` are composed of the sam
|
|
179
189
|
################
|
180
190
|
result = BCDD::Result::Success(:ok, my: 'value')
|
181
191
|
|
182
|
-
result.success?
|
183
|
-
result.failure?
|
184
|
-
result.type
|
185
|
-
result.
|
192
|
+
result.success? # true
|
193
|
+
result.failure? # false
|
194
|
+
result.type?(:ok) # true
|
195
|
+
result.type # :ok
|
196
|
+
result.value # {:my => "value"}
|
186
197
|
|
187
198
|
###################
|
188
199
|
# Without a value #
|
189
200
|
###################
|
190
201
|
result = BCDD::Result::Success(:yes)
|
191
202
|
|
192
|
-
result.success?
|
193
|
-
result.failure?
|
194
|
-
result.type
|
195
|
-
result.
|
203
|
+
result.success? # true
|
204
|
+
result.failure? # false
|
205
|
+
result.type?(:yes) # true
|
206
|
+
result.type # :yes
|
207
|
+
result.value # nil
|
196
208
|
```
|
197
209
|
|
198
210
|
**BCDD::Result::Failure**
|
@@ -203,26 +215,46 @@ result.value # nil
|
|
203
215
|
################
|
204
216
|
result = BCDD::Result::Failure(:err, 'my_value')
|
205
217
|
|
206
|
-
result.success?
|
207
|
-
result.failure?
|
208
|
-
result.type
|
209
|
-
result.
|
218
|
+
result.success? # false
|
219
|
+
result.failure? # true
|
220
|
+
result.type?(:err) # true
|
221
|
+
result.type # :err
|
222
|
+
result.value # "my_value"
|
210
223
|
|
211
224
|
###################
|
212
225
|
# Without a value #
|
213
226
|
###################
|
214
227
|
result = BCDD::Result::Failure(:no)
|
215
228
|
|
216
|
-
result.success?
|
217
|
-
result.failure?
|
218
|
-
result.type
|
219
|
-
result.
|
229
|
+
result.success? # false
|
230
|
+
result.failure? # true
|
231
|
+
result.type?(:no) # true
|
232
|
+
result.type # :no
|
233
|
+
result.value # nil
|
220
234
|
```
|
221
235
|
|
222
236
|
In both cases, the `type` must be a symbol, and the `value` can be any kind of object.
|
223
237
|
|
224
238
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
225
239
|
|
240
|
+
#### Checking types with `result.is?` or `method missing`
|
241
|
+
|
242
|
+
Beyond the `type?` method, you can also use the `is?` method to check the result type. If you want to check the type directly, you can write the type using a method that ends with a question mark.
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
result = BCDD::Result::Success(:ok)
|
246
|
+
|
247
|
+
result.is?(:ok) # true
|
248
|
+
result.ok? # true
|
249
|
+
|
250
|
+
result = BCDD::Result::Failure(:err)
|
251
|
+
|
252
|
+
result.is?(:err) # true
|
253
|
+
result.err? # true
|
254
|
+
```
|
255
|
+
|
256
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
257
|
+
|
226
258
|
#### Checking types with `result.success?` or `result.failure?`
|
227
259
|
|
228
260
|
`BCDD::Result#success?` and `BCDD::Result#failure?` are methods that allow you to check if the result is a success or a failure.
|
@@ -533,67 +565,6 @@ print_to_hash(**success_data) # [:success, :ok, 1]
|
|
533
565
|
|
534
566
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
535
567
|
|
536
|
-
### Pattern Matching
|
537
|
-
|
538
|
-
The `BCDD::Result` also provides support to pattern matching.
|
539
|
-
|
540
|
-
In the further examples, I will use the `Divide` lambda to exemplify its usage.
|
541
|
-
|
542
|
-
```ruby
|
543
|
-
Divide = lambda do |arg1, arg2|
|
544
|
-
arg1.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg1 must be numeric')
|
545
|
-
arg2.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg2 must be numeric')
|
546
|
-
|
547
|
-
return BCDD::Result::Failure(:division_by_zero, 'arg2 must not be zero') if arg2.zero?
|
548
|
-
|
549
|
-
BCDD::Result::Success(:division_completed, arg1 / arg2)
|
550
|
-
end
|
551
|
-
```
|
552
|
-
|
553
|
-
#### `Array`/`Find` patterns
|
554
|
-
|
555
|
-
```ruby
|
556
|
-
case Divide.call(4, 2)
|
557
|
-
in BCDD::Result::Failure[:invalid_arg, msg] then puts msg
|
558
|
-
in BCDD::Result::Failure[:division_by_zero, msg] then puts msg
|
559
|
-
in BCDD::Result::Success[:division_completed, value] then puts value
|
560
|
-
end
|
561
|
-
|
562
|
-
# The code above will print: 2
|
563
|
-
|
564
|
-
case Divide.call(4, 0)
|
565
|
-
in BCDD::Result::Failure[:invalid_arg, msg] then puts msg
|
566
|
-
in BCDD::Result::Failure[:division_by_zero, msg] then puts msg
|
567
|
-
in BCDD::Result::Success[:division_completed, value] then puts value
|
568
|
-
end
|
569
|
-
|
570
|
-
# The code above will print: arg2 must not be zero
|
571
|
-
```
|
572
|
-
|
573
|
-
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
574
|
-
|
575
|
-
#### `Hash` patterns
|
576
|
-
|
577
|
-
```ruby
|
578
|
-
case Divide.call(10, 2)
|
579
|
-
in { failure: { invalid_arg: msg } } then puts msg
|
580
|
-
in { failure: { division_by_zero: msg } } then puts msg
|
581
|
-
in { success: { division_completed: value } } then puts value
|
582
|
-
end
|
583
|
-
|
584
|
-
# The code above will print: 5
|
585
|
-
|
586
|
-
case Divide.call('10', 2)
|
587
|
-
in { failure: { invalid_arg: msg } } then puts msg
|
588
|
-
in { failure: { division_by_zero: msg } } then puts msg
|
589
|
-
in { success: { division_completed: value } } then puts value
|
590
|
-
end
|
591
|
-
|
592
|
-
# The code above will print: arg1 must be numeric
|
593
|
-
```
|
594
|
-
|
595
|
-
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
596
|
-
|
597
568
|
### Railway Oriented Programming
|
598
569
|
|
599
570
|
["Railway Oriented Programming (ROP)"](https://fsharpforfunandprofit.com/rop/) is a programming technique that involves linking blocks together to form a sequence of operations, also known as a pipeline.
|
@@ -1429,45 +1400,45 @@ result.success?(:ok)
|
|
1429
1400
|
|
1430
1401
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1431
1402
|
|
1432
|
-
### `BCDD::
|
1403
|
+
### `BCDD::Context`
|
1433
1404
|
|
1434
|
-
The `BCDD::
|
1405
|
+
The `BCDD::Context` is a `BCDD::Result`, meaning it has all the features of the `BCDD::Result`. The main difference is that it only accepts keyword arguments as a value, which applies to the `and_then`: The called methods must receive keyword arguments, and the dependency injection will be performed through keyword arguments.
|
1435
1406
|
|
1436
|
-
As the input/output are hashes, the results of each `and_then` call will automatically accumulate. This is useful in operations chaining, as the result of the previous operations will be automatically available for the next one. Because of this behavior, the `BCDD::
|
1407
|
+
As the input/output are hashes, the results of each `and_then` call will automatically accumulate. This is useful in operations chaining, as the result of the previous operations will be automatically available for the next one. Because of this behavior, the `BCDD::Context` has the `#and_expose` method to expose only the desired keys from the accumulated result.
|
1437
1408
|
|
1438
1409
|
#### Defining successes and failures
|
1439
1410
|
|
1440
|
-
As the `BCDD::Result`, you can declare success and failures directly from `BCDD::
|
1411
|
+
As the `BCDD::Result`, you can declare success and failures directly from `BCDD::Context`.
|
1441
1412
|
|
1442
1413
|
```ruby
|
1443
|
-
BCDD::
|
1444
|
-
#<BCDD::
|
1414
|
+
BCDD::Context::Success(:ok, a: 1, b: 2)
|
1415
|
+
#<BCDD::Context::Success type=:ok value={:a=>1, :b=>2}>
|
1445
1416
|
|
1446
|
-
BCDD::
|
1447
|
-
#<BCDD::
|
1417
|
+
BCDD::Context::Failure(:err, message: 'something went wrong')
|
1418
|
+
#<BCDD::Context::Failure type=:err value={:message=>"something went wrong"}>
|
1448
1419
|
```
|
1449
1420
|
|
1450
|
-
But different from `BCDD::Result` that accepts any value, the `BCDD::
|
1421
|
+
But different from `BCDD::Result` that accepts any value, the `BCDD::Context` only takes keyword arguments.
|
1451
1422
|
|
1452
1423
|
```ruby
|
1453
|
-
BCDD::
|
1424
|
+
BCDD::Context::Success(:ok, [1, 2])
|
1454
1425
|
# wrong number of arguments (given 2, expected 1) (ArgumentError)
|
1455
1426
|
|
1456
|
-
BCDD::
|
1427
|
+
BCDD::Context::Failure(:err, { message: 'something went wrong' })
|
1457
1428
|
# wrong number of arguments (given 2, expected 1) (ArgumentError)
|
1458
1429
|
|
1459
1430
|
#
|
1460
1431
|
# Use ** to convert a hash to keyword arguments
|
1461
1432
|
#
|
1462
|
-
BCDD::
|
1463
|
-
#<BCDD::
|
1433
|
+
BCDD::Context::Success(:ok, **{ message: 'hashes can be converted to keyword arguments' })
|
1434
|
+
#<BCDD::Context::Success type=:ok value={:message=>"hashes can be converted to keyword arguments"}>
|
1464
1435
|
```
|
1465
1436
|
|
1466
1437
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1467
1438
|
|
1468
1439
|
#### Constant aliases
|
1469
1440
|
|
1470
|
-
You can configure `Context` or `BCDD::Context` as an alias for `BCDD::
|
1441
|
+
You can configure `Context` or `BCDD::Context` as an alias for `BCDD::Context`. This is helpful to define a standard way to avoid the full constant name/path in your code.
|
1471
1442
|
|
1472
1443
|
```ruby
|
1473
1444
|
BCDD::Result.configuration do |config|
|
@@ -1481,9 +1452,9 @@ end
|
|
1481
1452
|
|
1482
1453
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1483
1454
|
|
1484
|
-
#### `BCDD::
|
1455
|
+
#### `BCDD::Context.mixin`
|
1485
1456
|
|
1486
|
-
As in the `BCDD::Result`, you can use the `BCDD::
|
1457
|
+
As in the `BCDD::Result`, you can use the `BCDD::Context.mixin` to add the `Success()` and `Failure()` methods to your classes/modules.
|
1487
1458
|
|
1488
1459
|
Let's see this feature and the data accumulation in action:
|
1489
1460
|
|
@@ -1493,7 +1464,7 @@ Let's see this feature and the data accumulation in action:
|
|
1493
1464
|
require 'logger'
|
1494
1465
|
|
1495
1466
|
class Divide
|
1496
|
-
include BCDD::
|
1467
|
+
include BCDD::Context.mixin
|
1497
1468
|
|
1498
1469
|
def call(arg1, arg2, logger: ::Logger.new(STDOUT))
|
1499
1470
|
validate_numbers(arg1, arg2)
|
@@ -1530,29 +1501,29 @@ end
|
|
1530
1501
|
|
1531
1502
|
Divide.new.call(10, 5)
|
1532
1503
|
# I, [2023-10-27T01:51:46.905004 #76915] INFO -- : The division result is 2
|
1533
|
-
#<BCDD::
|
1504
|
+
#<BCDD::Context::Success type=:ok value={:number=>2}>
|
1534
1505
|
|
1535
1506
|
Divide.new.call('10', 5)
|
1536
|
-
#<BCDD::
|
1507
|
+
#<BCDD::Context::Failure type=:err value={:message=>"arg1 must be numeric"}>
|
1537
1508
|
|
1538
1509
|
Divide.new.call(10, '5')
|
1539
|
-
#<BCDD::
|
1510
|
+
#<BCDD::Context::Failure type=:err value={:message=>"arg2 must be numeric"}>
|
1540
1511
|
|
1541
1512
|
Divide.new.call(10, 0)
|
1542
|
-
#<BCDD::
|
1513
|
+
#<BCDD::Context::Failure type=:err value={:message=>"arg2 must not be zero"}>
|
1543
1514
|
```
|
1544
1515
|
|
1545
1516
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1546
1517
|
|
1547
1518
|
##### `and_expose`
|
1548
1519
|
|
1549
|
-
This allows you to expose only the desired keys from the accumulated result. It can be used with any `BCDD::
|
1520
|
+
This allows you to expose only the desired keys from the accumulated result. It can be used with any `BCDD::Context` object.
|
1550
1521
|
|
1551
1522
|
Let's add it to the previous example:
|
1552
1523
|
|
1553
1524
|
```ruby
|
1554
1525
|
class Divide
|
1555
|
-
include BCDD::
|
1526
|
+
include BCDD::Context.mixin
|
1556
1527
|
|
1557
1528
|
def call(arg1, arg2)
|
1558
1529
|
validate_numbers(arg1, arg2)
|
@@ -1582,7 +1553,7 @@ class Divide
|
|
1582
1553
|
end
|
1583
1554
|
|
1584
1555
|
Divide.new.call(10, 5)
|
1585
|
-
#<BCDD::
|
1556
|
+
#<BCDD::Context::Success type=:division_completed value={:number=>2}>
|
1586
1557
|
```
|
1587
1558
|
|
1588
1559
|
As you can see, even with `divide` success exposing the division number with all the accumulated data (`**input`), the `#and_expose` could generate a new success with a new type and only with the desired keys.
|
@@ -1591,7 +1562,7 @@ Remove the `#and_expose` call to see the difference. This will be the outcome:
|
|
1591
1562
|
|
1592
1563
|
```ruby
|
1593
1564
|
Divide.new.call(10, 5)
|
1594
|
-
#<BCDD::
|
1565
|
+
#<BCDD::Context::Success type=:ok value={:number=>2, :number1=>10, :number2=>5}>
|
1595
1566
|
```
|
1596
1567
|
|
1597
1568
|
> PS: The `#and_expose` produces a terminal 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 `terminal: false` to `#and_expose`.
|
@@ -1600,11 +1571,11 @@ Divide.new.call(10, 5)
|
|
1600
1571
|
|
1601
1572
|
##### Module example (Singleton Methods)
|
1602
1573
|
|
1603
|
-
`BCDD::
|
1574
|
+
`BCDD::Context.mixin` can also produce singleton methods. Below is an example using a module (but it could be a class, too).
|
1604
1575
|
|
1605
1576
|
```ruby
|
1606
1577
|
module Divide
|
1607
|
-
extend self, BCDD::
|
1578
|
+
extend self, BCDD::Context.mixin
|
1608
1579
|
|
1609
1580
|
def call(arg1, arg2)
|
1610
1581
|
validate_numbers(arg1, arg2)
|
@@ -1634,29 +1605,29 @@ module Divide
|
|
1634
1605
|
end
|
1635
1606
|
|
1636
1607
|
Divide.call(10, 5)
|
1637
|
-
#<BCDD::
|
1608
|
+
#<BCDD::Context::Success type=:division_completed value={:number=>2}>
|
1638
1609
|
|
1639
1610
|
Divide.call('10', 5)
|
1640
|
-
#<BCDD::
|
1611
|
+
#<BCDD::Context::Failure type=:err value={:message=>"arg1 must be numeric"}>
|
1641
1612
|
|
1642
1613
|
Divide.call(10, '5')
|
1643
|
-
#<BCDD::
|
1614
|
+
#<BCDD::Context::Failure type=:err value={:message=>"arg2 must be numeric"}>
|
1644
1615
|
|
1645
1616
|
Divide.call(10, 0)
|
1646
|
-
#<BCDD::
|
1617
|
+
#<BCDD::Context::Failure type=:err value={:message=>"arg2 must not be zero"}>
|
1647
1618
|
```
|
1648
1619
|
|
1649
1620
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1650
1621
|
|
1651
|
-
#### `BCDD::
|
1622
|
+
#### `BCDD::Context::Expectations`
|
1652
1623
|
|
1653
|
-
The `BCDD::
|
1624
|
+
The `BCDD::Context::Expectations` is a `BCDD::Result::Expectations` with the `BCDD::Context` features.
|
1654
1625
|
|
1655
1626
|
This is an example using the mixin mode, but the standalone mode is also supported.
|
1656
1627
|
|
1657
1628
|
```ruby
|
1658
1629
|
class Divide
|
1659
|
-
include BCDD::
|
1630
|
+
include BCDD::Context::Expectations.mixin(
|
1660
1631
|
config: {
|
1661
1632
|
pattern_matching: { nil_as_valid_value_checking: true }
|
1662
1633
|
},
|
@@ -1680,16 +1651,16 @@ class Divide
|
|
1680
1651
|
end
|
1681
1652
|
|
1682
1653
|
Divide.new.call(10, 5)
|
1683
|
-
#<BCDD::
|
1654
|
+
#<BCDD::Context::Success type=:division_completed value={:number=>2}>
|
1684
1655
|
```
|
1685
1656
|
|
1686
|
-
As in the `BCDD::Result::Expectations.mixin`, the `BCDD::
|
1657
|
+
As in the `BCDD::Result::Expectations.mixin`, the `BCDD::Context::Expectations.mixin` will add a Result constant in the target class. It can generate success/failure results, which ensure the mixin expectations.
|
1687
1658
|
|
1688
1659
|
Let's see this using the previous example:
|
1689
1660
|
|
1690
1661
|
```ruby
|
1691
1662
|
Divide::Result::Success(:division_completed, number: 2)
|
1692
|
-
#<BCDD::
|
1663
|
+
#<BCDD::Context::Success type=:division_completed value={:number=>2}>
|
1693
1664
|
|
1694
1665
|
Divide::Result::Success(:division_completed, number: '2')
|
1695
1666
|
# value {:number=>"2"} is not allowed for :division_completed type ({:number=>"2"}: Numeric === "2" does not return true) (BCDD::Result::Contract::Error::UnexpectedValue)
|
@@ -1699,7 +1670,7 @@ Divide::Result::Success(:division_completed, number: '2')
|
|
1699
1670
|
|
1700
1671
|
#### Mixin add-ons
|
1701
1672
|
|
1702
|
-
The `BCDD::
|
1673
|
+
The `BCDD::Context.mixin` and `BCDD::Context::Expectations.mixin` also accepts the `config:` argument. And it works the same way as the `BCDD::Result` mixins.
|
1703
1674
|
|
1704
1675
|
**given**
|
1705
1676
|
|
@@ -1707,21 +1678,21 @@ This addon is enabled by default. It will create the `Given(*value)` method. Use
|
|
1707
1678
|
|
1708
1679
|
You can turn it off by passing `given: false` to the `config:` argument or using the `BCDD::Result.configuration`.
|
1709
1680
|
|
1710
|
-
The `Given()` addon for a BCDD::
|
1681
|
+
The `Given()` addon for a BCDD::Context can be called with one or more arguments. The arguments will be converted to a hash (`to_h`) and merged to define the first value of the result chain.
|
1711
1682
|
|
1712
1683
|
**continue**
|
1713
1684
|
|
1714
|
-
The `BCDD::
|
1685
|
+
The `BCDD::Context.mixin(config: { addon: { continue: true } })` or `BCDD::Context::Expectations.mixin(config: { addon: { continue: true } })` creates the `Continue(value)` method and change the `Success()` behavior to terminate the step chain.
|
1715
1686
|
|
1716
1687
|
So, if you want to advance to the next step, you must use `Continue(**value)` instead of `Success(type, **value)`. Otherwise, the step chain will be terminated.
|
1717
1688
|
|
1718
|
-
Let's use a mix of `BCDD::
|
1689
|
+
Let's use a mix of `BCDD::Context` features to see in action with this add-on:
|
1719
1690
|
|
1720
1691
|
```ruby
|
1721
1692
|
module Division
|
1722
1693
|
require 'logger'
|
1723
1694
|
|
1724
|
-
extend self, BCDD::
|
1695
|
+
extend self, BCDD::Context::Expectations.mixin(
|
1725
1696
|
config: {
|
1726
1697
|
addon: { continue: true },
|
1727
1698
|
pattern_matching: { nil_as_valid_value_checking: true }
|
@@ -1771,35 +1742,193 @@ end
|
|
1771
1742
|
|
1772
1743
|
Division.call(14, 2)
|
1773
1744
|
# I, [2023-10-27T02:01:05.812388 #77823] INFO -- : The division result is 7
|
1774
|
-
#<BCDD::
|
1745
|
+
#<BCDD::Context::Success type=:division_completed value={:number=>7}>
|
1775
1746
|
|
1776
1747
|
Division.call(0, 2)
|
1777
|
-
##<BCDD::
|
1748
|
+
##<BCDD::Context::Success type=:division_completed value={:number=>0}>
|
1778
1749
|
|
1779
1750
|
Division.call('14', 2)
|
1780
|
-
#<BCDD::
|
1751
|
+
#<BCDD::Context::Failure type=:invalid_arg value={:message=>"arg1 must be numeric"}>
|
1781
1752
|
|
1782
1753
|
Division.call(14, '2')
|
1783
|
-
#<BCDD::
|
1754
|
+
#<BCDD::Context::Failure type=:invalid_arg value={:message=>"arg2 must be numeric"}>
|
1784
1755
|
|
1785
1756
|
Division.call(14, 0)
|
1786
|
-
#<BCDD::
|
1757
|
+
#<BCDD::Context::Failure type=:division_by_zero value={:message=>"arg2 must not be zero"}>
|
1758
|
+
```
|
1759
|
+
|
1760
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1761
|
+
|
1762
|
+
## Pattern Matching
|
1763
|
+
|
1764
|
+
The `BCDD::Result` and `BCDD::Context` also provides support to pattern matching.
|
1765
|
+
|
1766
|
+
### `BCDD::Result`
|
1767
|
+
|
1768
|
+
In the further examples, I will use the `Divide` lambda to exemplify its usage.
|
1769
|
+
|
1770
|
+
```ruby
|
1771
|
+
Divide = lambda do |arg1, arg2|
|
1772
|
+
arg1.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg1 must be numeric')
|
1773
|
+
arg2.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg2 must be numeric')
|
1774
|
+
|
1775
|
+
return BCDD::Result::Failure(:division_by_zero, 'arg2 must not be zero') if arg2.zero?
|
1776
|
+
|
1777
|
+
BCDD::Result::Success(:division_completed, arg1 / arg2)
|
1778
|
+
end
|
1779
|
+
```
|
1780
|
+
|
1781
|
+
#### `Array`/`Find` patterns
|
1782
|
+
|
1783
|
+
```ruby
|
1784
|
+
case Divide.call(4, 2)
|
1785
|
+
in BCDD::Failure[:invalid_arg, msg] then puts msg
|
1786
|
+
in BCDD::Failure[:division_by_zero, msg] then puts msg
|
1787
|
+
in BCDD::Success[:division_completed, num] then puts num
|
1788
|
+
end
|
1789
|
+
|
1790
|
+
# The code above will print: 2
|
1791
|
+
|
1792
|
+
case Divide.call(4, 0)
|
1793
|
+
in BCDD::Failure[:invalid_arg, msg] then puts msg
|
1794
|
+
in BCDD::Failure[:division_by_zero, msg] then puts msg
|
1795
|
+
in BCDD::Success[:division_completed, num] then puts num
|
1796
|
+
end
|
1797
|
+
|
1798
|
+
# The code above will print: arg2 must not be zero
|
1799
|
+
```
|
1800
|
+
|
1801
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1802
|
+
|
1803
|
+
#### `Hash` patterns
|
1804
|
+
|
1805
|
+
```ruby
|
1806
|
+
case Divide.call(10, 2)
|
1807
|
+
in BCDD::Failure(type: :invalid_arg, value: msg) then puts msg
|
1808
|
+
in BCDD::Failure(type: :division_by_zero, value: msg) then puts msg
|
1809
|
+
in BCDD::Success(type: :division_completed, value: num) then puts num
|
1810
|
+
end
|
1811
|
+
|
1812
|
+
# The code above will print: 5
|
1813
|
+
|
1814
|
+
case Divide.call('10', 2)
|
1815
|
+
in BCDD::Failure(type: :invalid_arg, value: msg) then puts msg
|
1816
|
+
in BCDD::Failure(type: :division_by_zero, value: msg) then puts msg
|
1817
|
+
in BCDD::Success(type: :division_completed, value: num) then puts num
|
1818
|
+
end
|
1819
|
+
|
1820
|
+
# The code above will print: arg1 must be numeric
|
1821
|
+
```
|
1822
|
+
|
1823
|
+
You can also use `BCDD::Result::Success` and `BCDD::Result::Failure` as patterns.
|
1824
|
+
|
1825
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1826
|
+
|
1827
|
+
|
1828
|
+
### `BCDD::Context`
|
1829
|
+
|
1830
|
+
In the further examples, I will use the `Divide` lambda to exemplify its usage.
|
1831
|
+
|
1832
|
+
```ruby
|
1833
|
+
Divide = lambda do |arg1, arg2|
|
1834
|
+
arg1.is_a?(::Numeric) or return BCDD::Context::Failure(:invalid_arg, err: 'arg1 must be numeric')
|
1835
|
+
arg2.is_a?(::Numeric) or return BCDD::Context::Failure(:invalid_arg, err: 'arg2 must be numeric')
|
1836
|
+
|
1837
|
+
return BCDD::Context::Failure(:division_by_zero, err: 'arg2 must not be zero') if arg2.zero?
|
1838
|
+
|
1839
|
+
BCDD::Context::Success(:division_completed, num: arg1 / arg2)
|
1840
|
+
end
|
1841
|
+
```
|
1842
|
+
|
1843
|
+
#### `Array`/`Find` patterns
|
1844
|
+
|
1845
|
+
```ruby
|
1846
|
+
case Divide.call(4, 2)
|
1847
|
+
in BCDD::Failure[:invalid_arg, {msg:}] then puts msg
|
1848
|
+
in BCDD::Failure[:division_by_zero, {msg:}] then puts msg
|
1849
|
+
in BCDD::Success[:division_completed, {num:}] then puts num
|
1850
|
+
end
|
1851
|
+
|
1852
|
+
# The code above will print: 2
|
1853
|
+
|
1854
|
+
case Divide.call(4, 0)
|
1855
|
+
in BCDD::Failure[:invalid_arg, {msg:}] then puts msg
|
1856
|
+
in BCDD::Failure[:division_by_zero, {msg:}] then puts msg
|
1857
|
+
in BCDD::Success[:division_completed, {num:}] then puts num
|
1858
|
+
end
|
1859
|
+
|
1860
|
+
# The code above will print: arg2 must not be zero
|
1861
|
+
```
|
1862
|
+
|
1863
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1864
|
+
|
1865
|
+
#### `Hash` patterns
|
1866
|
+
|
1867
|
+
If you don't provide the keys :type and :value, the pattern will match the result value.
|
1868
|
+
|
1869
|
+
```ruby
|
1870
|
+
case Divide.call(10, 2)
|
1871
|
+
in BCDD::Failure({msg:}) then puts msg
|
1872
|
+
in BCDD::Success({num:}) then puts num
|
1873
|
+
end
|
1874
|
+
```
|
1875
|
+
|
1876
|
+
```ruby
|
1877
|
+
case Divide.call(10, 2)
|
1878
|
+
in BCDD::Failure(type: :invalid_arg, value: {msg:}) then puts msg
|
1879
|
+
in BCDD::Failure(type: :division_by_zero, value: {msg:}) then puts msg
|
1880
|
+
in BCDD::Success(type: :division_completed, value: {num:}) then puts num
|
1881
|
+
end
|
1882
|
+
|
1883
|
+
# The code above will print: 5
|
1884
|
+
|
1885
|
+
case Divide.call('10', 2)
|
1886
|
+
in BCDD::Failure(type: :invalid_arg, value: {msg:}) then puts {msg:}
|
1887
|
+
in BCDD::Failure(type: :division_by_zero, value: {msg:}) then puts msg
|
1888
|
+
in BCDD::Success(type: :division_completed, value: {num:}) then puts num
|
1889
|
+
end
|
1890
|
+
|
1891
|
+
# The code above will print: arg1 must be numeric
|
1787
1892
|
```
|
1788
1893
|
|
1894
|
+
You can also use `BCDD::Context::Success` and `BCDD::Context::Failure` as patterns.
|
1895
|
+
|
1789
1896
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1790
1897
|
|
1791
|
-
|
1898
|
+
### How to pattern match without the concept of success and failure
|
1899
|
+
|
1900
|
+
You can use the classes `BCDD::Result` and `BCDD::Context` as patterns, and the pattern matching will work without the concept of success and failure.
|
1901
|
+
|
1902
|
+
```ruby
|
1903
|
+
case Divide.call(10, 2)
|
1904
|
+
in BCDD::Context(:invalid_arg, {msg:}) then puts msg
|
1905
|
+
in BCDD::Context(:division_by_zero, {msg:}) then puts msg
|
1906
|
+
in BCDD::Context(:division_completed, {num:}) then puts num
|
1907
|
+
end
|
1908
|
+
|
1909
|
+
case Divide.call(10, 2)
|
1910
|
+
in BCDD::Result(:invalid_arg, msg) then puts msg
|
1911
|
+
in BCDD::Result(:division_by_zero, msg) then puts msg
|
1912
|
+
in BCDD::Result(:division_completed, num) then puts num
|
1913
|
+
end
|
1914
|
+
```
|
1792
1915
|
|
1793
|
-
|
1916
|
+
The `BCDD::Result` will also work with the `BCDD::Context`, but the opposite won't.
|
1794
1917
|
|
1795
|
-
|
1918
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1919
|
+
|
1920
|
+
## `BCDD::Result.event_logs`
|
1921
|
+
|
1922
|
+
Use `BCDD::Result.event_logs(&block)` to track all the results produced in the same or between different operations (it works with `BCDD::Result` and `BCDD::Context`). When there is a nesting of `event_logs` blocks, this mechanism will be able to correlate parent and child blocks and present the duration of all operations in milliseconds.
|
1923
|
+
|
1924
|
+
When you wrap the creation of the result with `BCDD::Result.event_logs`, the final one will expose all the event log records through the `BCDD::Result#event_logs` method.
|
1796
1925
|
|
1797
1926
|
```ruby
|
1798
1927
|
class Division
|
1799
1928
|
include BCDD::Result.mixin(config: { addon: { continue: true } })
|
1800
1929
|
|
1801
1930
|
def call(arg1, arg2)
|
1802
|
-
BCDD::Result.
|
1931
|
+
BCDD::Result.event_logs(name: 'Division', desc: 'divide two numbers') do
|
1803
1932
|
Given([arg1, arg2])
|
1804
1933
|
.and_then(:require_numbers)
|
1805
1934
|
.and_then(:check_for_zeros)
|
@@ -1835,7 +1964,7 @@ module SumDivisionsByTwo
|
|
1835
1964
|
extend self, BCDD::Result.mixin
|
1836
1965
|
|
1837
1966
|
def call(*numbers)
|
1838
|
-
BCDD::Result.
|
1967
|
+
BCDD::Result.event_logs(name: 'SumDivisionsByTwo') do
|
1839
1968
|
divisions = numbers.map { |number| Division.new.call(number, 2) }
|
1840
1969
|
|
1841
1970
|
if divisions.any?(&:failure?)
|
@@ -1854,14 +1983,17 @@ Let's see the result of the `SumDivisionsByTwo` call:
|
|
1854
1983
|
result = SumDivisionsByTwo.call(20, 10)
|
1855
1984
|
# => #<BCDD::Result::Success type=:sum value=15>
|
1856
1985
|
|
1857
|
-
result.
|
1986
|
+
result.event_logs
|
1858
1987
|
{
|
1859
1988
|
:version => 1,
|
1860
1989
|
:metadata => {
|
1861
1990
|
:duration => 0, # milliseconds
|
1862
1991
|
:trace_id => nil, # can be set through configuration
|
1863
|
-
:
|
1864
|
-
|
1992
|
+
:ids => {
|
1993
|
+
:tree => [0, [[1, []], [2, []]]],
|
1994
|
+
:matrix => { 0 => [0, 0], 1 => [1, 1], 2 => [2, 1]},
|
1995
|
+
:level_parent => { 0 => [0, 0], 1 => [1, 0], 2 => [1, 0]}
|
1996
|
+
}
|
1865
1997
|
},
|
1866
1998
|
:records=> [
|
1867
1999
|
{
|
@@ -1942,18 +2074,21 @@ result.transitions
|
|
1942
2074
|
|
1943
2075
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1944
2076
|
|
1945
|
-
### `
|
1946
|
-
|
1947
|
-
The `:ids_matrix`. It is a simplification of the `:ids_tree` property (a graph/tree representation of the transitions ids).
|
2077
|
+
### `metadata: {ids:}`
|
1948
2078
|
|
1949
|
-
The
|
2079
|
+
The `:ids` metadata property is a hash with three properties:
|
2080
|
+
- `:tree`, a graph/tree representation of the id of each `event_logs` block.
|
2081
|
+
- `:level_parent`, a hash with the level (depth) of each block and its parent id.
|
2082
|
+
- `:matrix`, a matrix representation of the event logs ids. It is a simplification of the `:tree` property.
|
1950
2083
|
|
1951
|
-
Use these data structures to build your own visualization
|
2084
|
+
Use these data structures to build your own visualization.
|
1952
2085
|
|
1953
|
-
> Check out [
|
2086
|
+
> Check out [Event Logs Listener example](examples/single_listener/lib/single_event_logs_listener.rb) to see how a listener can be used to build a STDOUT visualization, using these properties.
|
1954
2087
|
|
1955
2088
|
```ruby
|
1956
|
-
#
|
2089
|
+
# tree:
|
2090
|
+
# A graph representation (array of arrays) of the each event logs block id.
|
2091
|
+
#
|
1957
2092
|
0 # [0, [
|
1958
2093
|
|- 1 # [1, [[2, []]]],
|
1959
2094
|
| |- 2 # [3, []],
|
@@ -1964,7 +2099,24 @@ Use these data structures to build your own visualization of the transitions.
|
|
1964
2099
|
| |- 7 # [8, []]
|
1965
2100
|
|- 8 # ]]
|
1966
2101
|
|
1967
|
-
#
|
2102
|
+
# level_parent:
|
2103
|
+
# The event logs ids are the keys, and the level (depth) and parent id the values.
|
2104
|
+
# {
|
2105
|
+
0 # 0 => [0, 0],
|
2106
|
+
|- 1 # 1 => [1, 0],
|
2107
|
+
| |- 2 # 2 => [2, 1],
|
2108
|
+
|- 3 # 3 => [1, 0],
|
2109
|
+
|- 4 # 4 => [1, 0],
|
2110
|
+
| |- 5 # 5 => [2, 4],
|
2111
|
+
| |- 6 # 6 => [2, 4],
|
2112
|
+
| |- 7 # 7 => [3, 6],
|
2113
|
+
|- 8 # 8 => [1, 0]
|
2114
|
+
# }
|
2115
|
+
|
2116
|
+
# matrix:
|
2117
|
+
# The rows are the direct blocks from the root block,
|
2118
|
+
# and the columns are the nested blocks from the direct ones.
|
2119
|
+
# {
|
1968
2120
|
0 | 1 | 2 | 3 | 4 # 0 => [0, 0],
|
1969
2121
|
- | - | - | - | - # 1 => [1, 1],
|
1970
2122
|
0 | | | | # 2 => [1, 2],
|
@@ -1983,78 +2135,84 @@ Use these data structures to build your own visualization of the transitions.
|
|
1983
2135
|
|
1984
2136
|
#### Turning on/off
|
1985
2137
|
|
1986
|
-
You can use `BCDD::Result.config.feature.disable!(
|
2138
|
+
You can use `BCDD::Result.config.feature.disable!(event_logs)` and `BCDD::Result.config.feature.enable!(event_logs)` to turn on/off the `BCDD::Result.event_logs` feature.
|
1987
2139
|
|
1988
2140
|
```ruby
|
1989
2141
|
BCDD::Result.configuration do |config|
|
1990
|
-
config.feature.disable!(
|
2142
|
+
config.feature.disable!(event_logs)
|
1991
2143
|
end
|
1992
2144
|
|
1993
2145
|
result = SumDivisionsByTwo.call(20, 10)
|
1994
2146
|
# => #<BCDD::Result::Success type=:sum value=15>
|
1995
2147
|
|
1996
|
-
result.
|
1997
|
-
|
1998
|
-
|
2148
|
+
result.event_logs
|
2149
|
+
{
|
2150
|
+
:version=>1,
|
2151
|
+
:records=>[],
|
2152
|
+
:metadata=>{
|
2153
|
+
:duration=>0,
|
2154
|
+
:ids=>{:tree=>[], :matrix=>{}, :level_parent=>{}}, :trace_id=>nil
|
2155
|
+
}
|
2156
|
+
}
|
1999
2157
|
```
|
2000
2158
|
|
2001
2159
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
2002
2160
|
|
2003
2161
|
#### Setting a `trace_id` fetcher
|
2004
2162
|
|
2005
|
-
You can define a lambda (arity 0) to fetch the trace_id. This lambda will be called before the first
|
2163
|
+
You can define a lambda (arity 0) to fetch the trace_id. This lambda will be called before the first event logs block and will be used to set the `:trace_id` in the `:metadata` property.
|
2006
2164
|
|
2007
2165
|
Use to correlate different or the same operation (executed multiple times).
|
2008
2166
|
|
2009
2167
|
```ruby
|
2010
|
-
BCDD::Result.config.
|
2168
|
+
BCDD::Result.config.event_logs.trace_id = -> { Thread.current[:bcdd_result_event_logs_trace_id] }
|
2011
2169
|
```
|
2012
2170
|
|
2013
2171
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
2014
2172
|
|
2015
2173
|
#### Setting a `listener`
|
2016
2174
|
|
2017
|
-
You can define a listener to be called during the
|
2175
|
+
You can define a listener to be called during the event logs tracking (check out [this example](examples/single_listener/lib/single_event_logs_listener.rb)). It must be a class that includes `BCDD::Result::EventLogs::Listener`.
|
2018
2176
|
|
2019
|
-
Use it to build your additional logic on top of the
|
2020
|
-
- Log the
|
2021
|
-
- Perform
|
2022
|
-
- Instrument the
|
2023
|
-
- Build a visualization
|
2177
|
+
Use it to build your additional logic on top of the tracking. Examples:
|
2178
|
+
- Log the event logs.
|
2179
|
+
- Perform the tracing.
|
2180
|
+
- Instrument the event logs (measure/report).
|
2181
|
+
- Build a visualization (Diagrams, using the `records:` + `metadata: {ids:}` properties).
|
2024
2182
|
|
2025
|
-
After implementing your listener, you can set it to the `BCDD::Result.config.
|
2183
|
+
After implementing your listener, you can set it to the `BCDD::Result.config.event_logs.listener=`:
|
2026
2184
|
|
2027
2185
|
```ruby
|
2028
|
-
BCDD::Result.config.
|
2186
|
+
BCDD::Result.config.event_logs.listener = MyEventLogsListener
|
2029
2187
|
```
|
2030
2188
|
|
2031
2189
|
See the example below to understand how to implement one:
|
2032
2190
|
|
2033
2191
|
```ruby
|
2034
|
-
class
|
2035
|
-
include BCDD::Result::
|
2192
|
+
class MyEventLogsListener
|
2193
|
+
include BCDD::Result::EventLogs::Listener
|
2036
2194
|
|
2037
|
-
# A listener will be initialized before the first
|
2195
|
+
# A listener will be initialized before the first event logs block, and it is discarded after the last one.
|
2038
2196
|
def initialize
|
2039
2197
|
end
|
2040
2198
|
|
2041
|
-
# This method will be called before each
|
2042
|
-
# The parent
|
2199
|
+
# This method will be called before each event logs block.
|
2200
|
+
# The parent block will be called first in the case of nested ones.
|
2043
2201
|
#
|
2044
2202
|
# @param scope: {:id=>1, :name=>"SomeOperation", :desc=>"Optional description"}
|
2045
2203
|
def on_start(scope:)
|
2046
2204
|
end
|
2047
2205
|
|
2048
|
-
# This method will wrap all the
|
2049
|
-
# It can be used to perform an instrumentation (measure/report)
|
2206
|
+
# This method will wrap all the event logs in the same block.
|
2207
|
+
# It can be used to perform an instrumentation (measure/report).
|
2050
2208
|
#
|
2051
2209
|
# @param scope: {:id=>1, :name=>"SomeOperation", :desc=>"Optional description"}
|
2052
|
-
def
|
2210
|
+
def around_event_logs(scope:)
|
2053
2211
|
yield
|
2054
2212
|
end
|
2055
2213
|
|
2056
2214
|
# This method will wrap each and_then call.
|
2057
|
-
# It can be used to perform an instrumentation
|
2215
|
+
# It can be used to perform an instrumentation of the and_then calls.
|
2058
2216
|
#
|
2059
2217
|
# @param scope: {:id=>1, :name=>"SomeOperation", :desc=>"Optional description"}
|
2060
2218
|
# @param and_then:
|
@@ -2078,29 +2236,32 @@ class MyTransitionsListener
|
|
2078
2236
|
def on_record(record:)
|
2079
2237
|
end
|
2080
2238
|
|
2081
|
-
# This method will be called at the end of the
|
2239
|
+
# This method will be called at the end of the event logs tracking.
|
2082
2240
|
#
|
2083
|
-
# @param
|
2241
|
+
# @param event_logs:
|
2084
2242
|
# {
|
2085
2243
|
# :version => 1,
|
2086
2244
|
# :metadata => {
|
2087
2245
|
# :duration => 0,
|
2088
2246
|
# :trace_id => nil,
|
2089
|
-
# :
|
2090
|
-
#
|
2247
|
+
# :ids => {
|
2248
|
+
# :tree => [0, [[1, []], [2, []]]],
|
2249
|
+
# :matrix => { 0 => [0, 0], 1 => [1, 1], 2 => [2, 1]},
|
2250
|
+
# :level_parent => { 0 => [0, 0], 1 => [1, 0], 2 => [1, 0]}
|
2251
|
+
# }
|
2091
2252
|
# },
|
2092
2253
|
# :records => [
|
2093
2254
|
# # ...
|
2094
2255
|
# ]
|
2095
2256
|
# }
|
2096
|
-
def on_finish(
|
2257
|
+
def on_finish(event_logs:)
|
2097
2258
|
end
|
2098
2259
|
|
2099
|
-
# This method will be called when an exception is raised during the
|
2260
|
+
# This method will be called when an exception is raised during the event logs tracking.
|
2100
2261
|
#
|
2101
2262
|
# @param exception: Exception
|
2102
|
-
# @param
|
2103
|
-
def before_interruption(exception:,
|
2263
|
+
# @param event_logs: Hash
|
2264
|
+
def before_interruption(exception:, event_logs:)
|
2104
2265
|
end
|
2105
2266
|
end
|
2106
2267
|
```
|
@@ -2109,15 +2270,15 @@ end
|
|
2109
2270
|
|
2110
2271
|
#### Setting multiple `listeners`
|
2111
2272
|
|
2112
|
-
You can use `BCDD::Result::
|
2273
|
+
You can use `BCDD::Result::EventLogs::Listeners[]` to creates a listener of listeners (check out [this example](examples/multiple_listeners/Rakefile)), which will be called in the order they were added.
|
2113
2274
|
|
2114
|
-
**Attention:** It only allows one listener to handle `around_and_then` and another `
|
2275
|
+
**Attention:** It only allows one listener to handle `around_and_then` and another `around_event_logs` records.
|
2115
2276
|
|
2116
|
-
> The example below defines different listeners to handle `around_and_then` and `
|
2277
|
+
> The example below defines different listeners to handle `around_and_then` and `around_event_logs,` but it is also possible to define a listener to handle both.
|
2117
2278
|
|
2118
2279
|
```ruby
|
2119
2280
|
class AroundAndThenListener
|
2120
|
-
include BCDD::Result::
|
2281
|
+
include BCDD::Result::EventLogs::Listener
|
2121
2282
|
|
2122
2283
|
# It must be a static/singleton method.
|
2123
2284
|
def self.around_and_then?
|
@@ -2129,21 +2290,21 @@ class AroundAndThenListener
|
|
2129
2290
|
end
|
2130
2291
|
end
|
2131
2292
|
|
2132
|
-
class
|
2133
|
-
include BCDD::Result::
|
2293
|
+
class AroundEventLogsListener
|
2294
|
+
include BCDD::Result::EventLogs::Listener
|
2134
2295
|
|
2135
2296
|
# It must be a static/singleton method.
|
2136
|
-
def self.
|
2297
|
+
def self.around_event_logs?
|
2137
2298
|
true
|
2138
2299
|
end
|
2139
2300
|
|
2140
|
-
def
|
2301
|
+
def around_event_logs(scope:)
|
2141
2302
|
#...
|
2142
2303
|
end
|
2143
2304
|
end
|
2144
2305
|
|
2145
|
-
class
|
2146
|
-
include BCDD::Result::
|
2306
|
+
class MyEventLogsListener
|
2307
|
+
include BCDD::Result::EventLogs::Listener
|
2147
2308
|
end
|
2148
2309
|
```
|
2149
2310
|
|
@@ -2151,20 +2312,20 @@ How to use it:
|
|
2151
2312
|
|
2152
2313
|
```ruby
|
2153
2314
|
# The listeners will be called in the order they were added.
|
2154
|
-
BCDD::Result.config.
|
2155
|
-
|
2315
|
+
BCDD::Result.config.event_logs.listener = BCDD::Result::EventLogs::Listeners[
|
2316
|
+
MyEventLogsListener,
|
2156
2317
|
AroundAndThenListener,
|
2157
|
-
|
2318
|
+
AroundEventLogsListener
|
2158
2319
|
]
|
2159
2320
|
```
|
2160
2321
|
|
2161
|
-
> Check out [this example](examples/multiple_listeners) to see a listener to print the
|
2322
|
+
> Check out [this example](examples/multiple_listeners) to see a listener to print the event logs and another to store them in the database.
|
2162
2323
|
|
2163
2324
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
2164
2325
|
|
2165
2326
|
## `BCDD::Result.configuration`
|
2166
2327
|
|
2167
|
-
The `BCDD::Result.configuration` allows you to configure default behaviors for `BCDD::Result` and `BCDD::
|
2328
|
+
The `BCDD::Result.configuration` allows you to configure default behaviors for `BCDD::Result` and `BCDD::Context` through a configuration block. After using it, the configuration is frozen, ensuring the expected behaviors for your application.
|
2168
2329
|
|
2169
2330
|
> Note: You can use `BCDD::Result.configuration(freeze: false) {}` to avoid the freezing. This can be useful in tests. Please be sure to use it with caution.
|
2170
2331
|
|
@@ -2186,13 +2347,13 @@ Let's see what each configuration in the example above does:
|
|
2186
2347
|
|
2187
2348
|
### `config.addon.enable!(:given, :continue)` <!-- omit in toc -->
|
2188
2349
|
|
2189
|
-
This configuration enables the `Continue()` method for `BCDD::Result.mixin`, `BCDD::
|
2350
|
+
This configuration enables the `Continue()` method for `BCDD::Result.mixin`, `BCDD::Context.mixin`, `BCDD::Result::Expectation.mixin`, and `BCDD::Context::Expectation.mixin`. Link to documentations: [(1)](#add-ons) [(2)](#mixin-add-ons).
|
2190
2351
|
|
2191
2352
|
It is also enabling the `Given()` which is already enabled by default. Link to documentation: [(1)](#add-ons) [(2)](#mixin-add-ons).
|
2192
2353
|
|
2193
2354
|
### `config.constant_alias.enable!('Result', 'BCDD::Context')` <!-- omit in toc -->
|
2194
2355
|
|
2195
|
-
This configuration make `Result` a constant alias for `BCDD::Result`, and `BCDD::Context` a constant alias for `BCDD::
|
2356
|
+
This configuration make `Result` a constant alias for `BCDD::Result`, and `BCDD::Context` a constant alias for `BCDD::Context`.
|
2196
2357
|
|
2197
2358
|
Link to documentations:
|
2198
2359
|
- [Result alias](#bcddresult-versus-result)
|
@@ -2200,11 +2361,11 @@ Link to documentations:
|
|
2200
2361
|
|
2201
2362
|
### `config.pattern_matching.disable!(:nil_as_valid_value_checking)` <!-- omit in toc -->
|
2202
2363
|
|
2203
|
-
This configuration disables the `nil_as_valid_value_checking` for `BCDD::Result` and `BCDD::
|
2364
|
+
This configuration disables the `nil_as_valid_value_checking` for `BCDD::Result` and `BCDD::Context`. Link to [documentation](#pattern-matching-support).
|
2204
2365
|
|
2205
2366
|
### `config.feature.disable!(:expectations)` <!-- omit in toc -->
|
2206
2367
|
|
2207
|
-
This configuration turns off the expectations for `BCDD::Result` and `BCDD::
|
2368
|
+
This configuration turns off the expectations for `BCDD::Result` and `BCDD::Context`. The expectations are helpful in development and test environments, but they can be disabled in production environments for performance gain.
|
2208
2369
|
|
2209
2370
|
PS: I'm using `::Rails.env.production?` to check the environment, but you can use any logic you want.
|
2210
2371
|
|
@@ -2226,18 +2387,18 @@ BCDD::Result.config.addon.options
|
|
2226
2387
|
# :enabled=>false,
|
2227
2388
|
# :affects=>[
|
2228
2389
|
# "BCDD::Result.mixin",
|
2229
|
-
# "BCDD::
|
2390
|
+
# "BCDD::Context.mixin",
|
2230
2391
|
# "BCDD::Result::Expectations.mixin",
|
2231
|
-
# "BCDD::
|
2392
|
+
# "BCDD::Context::Expectations.mixin"
|
2232
2393
|
# ]
|
2233
2394
|
# },
|
2234
2395
|
# :given=>{
|
2235
2396
|
# :enabled=>true,
|
2236
2397
|
# :affects=>[
|
2237
2398
|
# "BCDD::Result.mixin",
|
2238
|
-
# "BCDD::
|
2399
|
+
# "BCDD::Context.mixin",
|
2239
2400
|
# "BCDD::Result::Expectations.mixin",
|
2240
|
-
# "BCDD::
|
2401
|
+
# "BCDD::Context::Expectations.mixin"
|
2241
2402
|
# ]
|
2242
2403
|
# }
|
2243
2404
|
# }
|
@@ -2269,7 +2430,7 @@ BCDD::Result.config.pattern_matching.options
|
|
2269
2430
|
# :enabled=>false,
|
2270
2431
|
# :affects=>[
|
2271
2432
|
# "BCDD::Result::Expectations,
|
2272
|
-
# "BCDD::
|
2433
|
+
# "BCDD::Context::Expectations"
|
2273
2434
|
# ]
|
2274
2435
|
# }
|
2275
2436
|
# }
|
@@ -2286,21 +2447,21 @@ BCDD::Result.config.feature.options
|
|
2286
2447
|
# :enabled=>true,
|
2287
2448
|
# :affects=>[
|
2288
2449
|
# "BCDD::Result::Expectations,
|
2289
|
-
# "BCDD::
|
2450
|
+
# "BCDD::Context::Expectations"
|
2290
2451
|
# ]
|
2291
2452
|
# },
|
2292
|
-
#
|
2453
|
+
# event_logs=>{
|
2293
2454
|
# :enabled=>true,
|
2294
2455
|
# :affects=>[
|
2295
2456
|
# "BCDD::Result",
|
2296
|
-
# "BCDD::
|
2457
|
+
# "BCDD::Context"
|
2297
2458
|
# ]
|
2298
2459
|
# },
|
2299
2460
|
# :and_then!=>{
|
2300
2461
|
# :enabled=>false,
|
2301
2462
|
# :affects=>[
|
2302
2463
|
# "BCDD::Result",
|
2303
|
-
# "BCDD::
|
2464
|
+
# "BCDD::Context"
|
2304
2465
|
# ]
|
2305
2466
|
# },
|
2306
2467
|
# }
|
@@ -2343,7 +2504,7 @@ class PlaceOrder < Micro::Case
|
|
2343
2504
|
end
|
2344
2505
|
```
|
2345
2506
|
|
2346
|
-
To facilitate migration for users accustomed to the above approaches, `bcdd-result` includes the `BCDD::Result#and_then!`/`BCDD::
|
2507
|
+
To facilitate migration for users accustomed to the above approaches, `bcdd-result` includes the `BCDD::Result#and_then!`/`BCDD::Context#and_then!` methods, which will invoke the method `call` of the given operation and expect it to return a `BCDD::Result`/`BCDD::Context` object.
|
2347
2508
|
|
2348
2509
|
```ruby
|
2349
2510
|
BCDD::Result.configure do |config|
|
@@ -2351,7 +2512,7 @@ BCDD::Result.configure do |config|
|
|
2351
2512
|
end
|
2352
2513
|
|
2353
2514
|
class PlaceOrder
|
2354
|
-
include BCDD::
|
2515
|
+
include BCDD::Context.mixin
|
2355
2516
|
|
2356
2517
|
def call(**input)
|
2357
2518
|
Given(input)
|
@@ -2383,11 +2544,11 @@ class PlaceOrder
|
|
2383
2544
|
end
|
2384
2545
|
```
|
2385
2546
|
|
2386
|
-
**In BCDD::
|
2547
|
+
**In BCDD::Context**
|
2387
2548
|
|
2388
2549
|
```ruby
|
2389
2550
|
class PlaceOrder
|
2390
|
-
include BCDD::
|
2551
|
+
include BCDD::Context.mixin
|
2391
2552
|
|
2392
2553
|
def call(logger:, **input)
|
2393
2554
|
Given(input)
|
@@ -2441,7 +2602,7 @@ Attention: to ensure the correct behavior, do not mix `#and_then` and `#and_then
|
|
2441
2602
|
|
2442
2603
|
#### Analysis: Why is `#and_then` the antidote/standard?
|
2443
2604
|
|
2444
|
-
The `BCDD::Result#and_then`/`BCDD::
|
2605
|
+
The `BCDD::Result#and_then`/`BCDD::Context#and_then` methods diverge from the above approach by requiring explicit invocation and mapping of the outcomes at each process step. This approach has the following advantages:
|
2445
2606
|
|
2446
2607
|
- **Clarity:** The input/output relationship between the steps is apparent and highly understandable.
|
2447
2608
|
|
@@ -2451,7 +2612,7 @@ See this example to understand what your code should look like:
|
|
2451
2612
|
|
2452
2613
|
```ruby
|
2453
2614
|
class PlaceOrder
|
2454
|
-
include BCDD::
|
2615
|
+
include BCDD::Context.mixin(config: { addon: { continue: true } })
|
2455
2616
|
|
2456
2617
|
def call(**input)
|
2457
2618
|
Given(input)
|