bcdd-result 0.13.0 → 1.1.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 +61 -21
- data/README.md +397 -227
- 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 +6 -5
- 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 +11 -11
- data/lib/bcdd/context.rb +115 -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
|
-
- [
|
69
|
-
- [`BCDD::
|
66
|
+
- [Hash methods](#hash-methods)
|
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,61 +1400,70 @@ 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
|
+
#### Hash methods
|
1440
|
+
|
1441
|
+
The `BCDD::Context` only accepts hashes as its values. Because of this, its instances have some Hash's methods to query/access the values. The available methods are:
|
1469
1442
|
|
1470
|
-
|
1443
|
+
- `#slice` to extract only the desired keys.
|
1444
|
+
- `#[]`, `#dig`, `#fetch` to access the values.
|
1445
|
+
- `#values_at` and `#fetch_values` to get the values of the desired keys.
|
1471
1446
|
|
1472
1447
|
```ruby
|
1473
|
-
BCDD::
|
1474
|
-
config.context_alias.enable!('BCDD::Context')
|
1448
|
+
result = BCDD::Context::Success(:ok, a: 1, b: 2, c: {d: 4})
|
1475
1449
|
|
1476
|
-
|
1450
|
+
result[:a] # 1
|
1451
|
+
result.fetch(:a) # 1
|
1452
|
+
result.dig(:c, :d) # 4
|
1477
1453
|
|
1478
|
-
|
1479
|
-
|
1454
|
+
result.slice(:a, :b) # {:a=>1, :b=>2}
|
1455
|
+
|
1456
|
+
result.values_at(:a, :b) # [1, 2]
|
1457
|
+
result.fetch_values(:a, :b) # [1, 2]
|
1480
1458
|
```
|
1481
1459
|
|
1460
|
+
These methods are available for `BCDD::Context::Success` and `BCDD::Context::Failure` instances.
|
1461
|
+
|
1482
1462
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1483
1463
|
|
1484
|
-
#### `BCDD::
|
1464
|
+
#### `BCDD::Context.mixin`
|
1485
1465
|
|
1486
|
-
As in the `BCDD::Result`, you can use the `BCDD::
|
1466
|
+
As in the `BCDD::Result`, you can use the `BCDD::Context.mixin` to add the `Success()` and `Failure()` methods to your classes/modules.
|
1487
1467
|
|
1488
1468
|
Let's see this feature and the data accumulation in action:
|
1489
1469
|
|
@@ -1493,7 +1473,7 @@ Let's see this feature and the data accumulation in action:
|
|
1493
1473
|
require 'logger'
|
1494
1474
|
|
1495
1475
|
class Divide
|
1496
|
-
include BCDD::
|
1476
|
+
include BCDD::Context.mixin
|
1497
1477
|
|
1498
1478
|
def call(arg1, arg2, logger: ::Logger.new(STDOUT))
|
1499
1479
|
validate_numbers(arg1, arg2)
|
@@ -1530,29 +1510,29 @@ end
|
|
1530
1510
|
|
1531
1511
|
Divide.new.call(10, 5)
|
1532
1512
|
# I, [2023-10-27T01:51:46.905004 #76915] INFO -- : The division result is 2
|
1533
|
-
#<BCDD::
|
1513
|
+
#<BCDD::Context::Success type=:ok value={:number=>2}>
|
1534
1514
|
|
1535
1515
|
Divide.new.call('10', 5)
|
1536
|
-
#<BCDD::
|
1516
|
+
#<BCDD::Context::Failure type=:err value={:message=>"arg1 must be numeric"}>
|
1537
1517
|
|
1538
1518
|
Divide.new.call(10, '5')
|
1539
|
-
#<BCDD::
|
1519
|
+
#<BCDD::Context::Failure type=:err value={:message=>"arg2 must be numeric"}>
|
1540
1520
|
|
1541
1521
|
Divide.new.call(10, 0)
|
1542
|
-
#<BCDD::
|
1522
|
+
#<BCDD::Context::Failure type=:err value={:message=>"arg2 must not be zero"}>
|
1543
1523
|
```
|
1544
1524
|
|
1545
1525
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1546
1526
|
|
1547
1527
|
##### `and_expose`
|
1548
1528
|
|
1549
|
-
This allows you to expose only the desired keys from the accumulated result. It can be used with any `BCDD::
|
1529
|
+
This allows you to expose only the desired keys from the accumulated result. It can be used with any `BCDD::Context` object.
|
1550
1530
|
|
1551
1531
|
Let's add it to the previous example:
|
1552
1532
|
|
1553
1533
|
```ruby
|
1554
1534
|
class Divide
|
1555
|
-
include BCDD::
|
1535
|
+
include BCDD::Context.mixin
|
1556
1536
|
|
1557
1537
|
def call(arg1, arg2)
|
1558
1538
|
validate_numbers(arg1, arg2)
|
@@ -1582,7 +1562,7 @@ class Divide
|
|
1582
1562
|
end
|
1583
1563
|
|
1584
1564
|
Divide.new.call(10, 5)
|
1585
|
-
#<BCDD::
|
1565
|
+
#<BCDD::Context::Success type=:division_completed value={:number=>2}>
|
1586
1566
|
```
|
1587
1567
|
|
1588
1568
|
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 +1571,7 @@ Remove the `#and_expose` call to see the difference. This will be the outcome:
|
|
1591
1571
|
|
1592
1572
|
```ruby
|
1593
1573
|
Divide.new.call(10, 5)
|
1594
|
-
#<BCDD::
|
1574
|
+
#<BCDD::Context::Success type=:ok value={:number=>2, :number1=>10, :number2=>5}>
|
1595
1575
|
```
|
1596
1576
|
|
1597
1577
|
> 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 +1580,11 @@ Divide.new.call(10, 5)
|
|
1600
1580
|
|
1601
1581
|
##### Module example (Singleton Methods)
|
1602
1582
|
|
1603
|
-
`BCDD::
|
1583
|
+
`BCDD::Context.mixin` can also produce singleton methods. Below is an example using a module (but it could be a class, too).
|
1604
1584
|
|
1605
1585
|
```ruby
|
1606
1586
|
module Divide
|
1607
|
-
extend self, BCDD::
|
1587
|
+
extend self, BCDD::Context.mixin
|
1608
1588
|
|
1609
1589
|
def call(arg1, arg2)
|
1610
1590
|
validate_numbers(arg1, arg2)
|
@@ -1634,29 +1614,29 @@ module Divide
|
|
1634
1614
|
end
|
1635
1615
|
|
1636
1616
|
Divide.call(10, 5)
|
1637
|
-
#<BCDD::
|
1617
|
+
#<BCDD::Context::Success type=:division_completed value={:number=>2}>
|
1638
1618
|
|
1639
1619
|
Divide.call('10', 5)
|
1640
|
-
#<BCDD::
|
1620
|
+
#<BCDD::Context::Failure type=:err value={:message=>"arg1 must be numeric"}>
|
1641
1621
|
|
1642
1622
|
Divide.call(10, '5')
|
1643
|
-
#<BCDD::
|
1623
|
+
#<BCDD::Context::Failure type=:err value={:message=>"arg2 must be numeric"}>
|
1644
1624
|
|
1645
1625
|
Divide.call(10, 0)
|
1646
|
-
#<BCDD::
|
1626
|
+
#<BCDD::Context::Failure type=:err value={:message=>"arg2 must not be zero"}>
|
1647
1627
|
```
|
1648
1628
|
|
1649
1629
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1650
1630
|
|
1651
|
-
#### `BCDD::
|
1631
|
+
#### `BCDD::Context::Expectations`
|
1652
1632
|
|
1653
|
-
The `BCDD::
|
1633
|
+
The `BCDD::Context::Expectations` is a `BCDD::Result::Expectations` with the `BCDD::Context` features.
|
1654
1634
|
|
1655
1635
|
This is an example using the mixin mode, but the standalone mode is also supported.
|
1656
1636
|
|
1657
1637
|
```ruby
|
1658
1638
|
class Divide
|
1659
|
-
include BCDD::
|
1639
|
+
include BCDD::Context::Expectations.mixin(
|
1660
1640
|
config: {
|
1661
1641
|
pattern_matching: { nil_as_valid_value_checking: true }
|
1662
1642
|
},
|
@@ -1680,16 +1660,16 @@ class Divide
|
|
1680
1660
|
end
|
1681
1661
|
|
1682
1662
|
Divide.new.call(10, 5)
|
1683
|
-
#<BCDD::
|
1663
|
+
#<BCDD::Context::Success type=:division_completed value={:number=>2}>
|
1684
1664
|
```
|
1685
1665
|
|
1686
|
-
As in the `BCDD::Result::Expectations.mixin`, the `BCDD::
|
1666
|
+
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
1667
|
|
1688
1668
|
Let's see this using the previous example:
|
1689
1669
|
|
1690
1670
|
```ruby
|
1691
1671
|
Divide::Result::Success(:division_completed, number: 2)
|
1692
|
-
#<BCDD::
|
1672
|
+
#<BCDD::Context::Success type=:division_completed value={:number=>2}>
|
1693
1673
|
|
1694
1674
|
Divide::Result::Success(:division_completed, number: '2')
|
1695
1675
|
# 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 +1679,7 @@ Divide::Result::Success(:division_completed, number: '2')
|
|
1699
1679
|
|
1700
1680
|
#### Mixin add-ons
|
1701
1681
|
|
1702
|
-
The `BCDD::
|
1682
|
+
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
1683
|
|
1704
1684
|
**given**
|
1705
1685
|
|
@@ -1707,21 +1687,21 @@ This addon is enabled by default. It will create the `Given(*value)` method. Use
|
|
1707
1687
|
|
1708
1688
|
You can turn it off by passing `given: false` to the `config:` argument or using the `BCDD::Result.configuration`.
|
1709
1689
|
|
1710
|
-
The `Given()` addon for a BCDD::
|
1690
|
+
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
1691
|
|
1712
1692
|
**continue**
|
1713
1693
|
|
1714
|
-
The `BCDD::
|
1694
|
+
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
1695
|
|
1716
1696
|
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
1697
|
|
1718
|
-
Let's use a mix of `BCDD::
|
1698
|
+
Let's use a mix of `BCDD::Context` features to see in action with this add-on:
|
1719
1699
|
|
1720
1700
|
```ruby
|
1721
1701
|
module Division
|
1722
1702
|
require 'logger'
|
1723
1703
|
|
1724
|
-
extend self, BCDD::
|
1704
|
+
extend self, BCDD::Context::Expectations.mixin(
|
1725
1705
|
config: {
|
1726
1706
|
addon: { continue: true },
|
1727
1707
|
pattern_matching: { nil_as_valid_value_checking: true }
|
@@ -1771,35 +1751,193 @@ end
|
|
1771
1751
|
|
1772
1752
|
Division.call(14, 2)
|
1773
1753
|
# I, [2023-10-27T02:01:05.812388 #77823] INFO -- : The division result is 7
|
1774
|
-
#<BCDD::
|
1754
|
+
#<BCDD::Context::Success type=:division_completed value={:number=>7}>
|
1775
1755
|
|
1776
1756
|
Division.call(0, 2)
|
1777
|
-
##<BCDD::
|
1757
|
+
##<BCDD::Context::Success type=:division_completed value={:number=>0}>
|
1778
1758
|
|
1779
1759
|
Division.call('14', 2)
|
1780
|
-
#<BCDD::
|
1760
|
+
#<BCDD::Context::Failure type=:invalid_arg value={:message=>"arg1 must be numeric"}>
|
1781
1761
|
|
1782
1762
|
Division.call(14, '2')
|
1783
|
-
#<BCDD::
|
1763
|
+
#<BCDD::Context::Failure type=:invalid_arg value={:message=>"arg2 must be numeric"}>
|
1784
1764
|
|
1785
1765
|
Division.call(14, 0)
|
1786
|
-
#<BCDD::
|
1766
|
+
#<BCDD::Context::Failure type=:division_by_zero value={:message=>"arg2 must not be zero"}>
|
1787
1767
|
```
|
1788
1768
|
|
1789
1769
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1790
1770
|
|
1791
|
-
##
|
1771
|
+
## Pattern Matching
|
1792
1772
|
|
1793
|
-
|
1773
|
+
The `BCDD::Result` and `BCDD::Context` also provides support to pattern matching.
|
1794
1774
|
|
1795
|
-
|
1775
|
+
### `BCDD::Result`
|
1776
|
+
|
1777
|
+
In the further examples, I will use the `Divide` lambda to exemplify its usage.
|
1778
|
+
|
1779
|
+
```ruby
|
1780
|
+
Divide = lambda do |arg1, arg2|
|
1781
|
+
arg1.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg1 must be numeric')
|
1782
|
+
arg2.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg2 must be numeric')
|
1783
|
+
|
1784
|
+
return BCDD::Result::Failure(:division_by_zero, 'arg2 must not be zero') if arg2.zero?
|
1785
|
+
|
1786
|
+
BCDD::Result::Success(:division_completed, arg1 / arg2)
|
1787
|
+
end
|
1788
|
+
```
|
1789
|
+
|
1790
|
+
#### `Array`/`Find` patterns
|
1791
|
+
|
1792
|
+
```ruby
|
1793
|
+
case Divide.call(4, 2)
|
1794
|
+
in BCDD::Failure[:invalid_arg, msg] then puts msg
|
1795
|
+
in BCDD::Failure[:division_by_zero, msg] then puts msg
|
1796
|
+
in BCDD::Success[:division_completed, num] then puts num
|
1797
|
+
end
|
1798
|
+
|
1799
|
+
# The code above will print: 2
|
1800
|
+
|
1801
|
+
case Divide.call(4, 0)
|
1802
|
+
in BCDD::Failure[:invalid_arg, msg] then puts msg
|
1803
|
+
in BCDD::Failure[:division_by_zero, msg] then puts msg
|
1804
|
+
in BCDD::Success[:division_completed, num] then puts num
|
1805
|
+
end
|
1806
|
+
|
1807
|
+
# The code above will print: arg2 must not be zero
|
1808
|
+
```
|
1809
|
+
|
1810
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1811
|
+
|
1812
|
+
#### `Hash` patterns
|
1813
|
+
|
1814
|
+
```ruby
|
1815
|
+
case Divide.call(10, 2)
|
1816
|
+
in BCDD::Failure(type: :invalid_arg, value: msg) then puts msg
|
1817
|
+
in BCDD::Failure(type: :division_by_zero, value: msg) then puts msg
|
1818
|
+
in BCDD::Success(type: :division_completed, value: num) then puts num
|
1819
|
+
end
|
1820
|
+
|
1821
|
+
# The code above will print: 5
|
1822
|
+
|
1823
|
+
case Divide.call('10', 2)
|
1824
|
+
in BCDD::Failure(type: :invalid_arg, value: msg) then puts msg
|
1825
|
+
in BCDD::Failure(type: :division_by_zero, value: msg) then puts msg
|
1826
|
+
in BCDD::Success(type: :division_completed, value: num) then puts num
|
1827
|
+
end
|
1828
|
+
|
1829
|
+
# The code above will print: arg1 must be numeric
|
1830
|
+
```
|
1831
|
+
|
1832
|
+
You can also use `BCDD::Result::Success` and `BCDD::Result::Failure` as patterns.
|
1833
|
+
|
1834
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1835
|
+
|
1836
|
+
|
1837
|
+
### `BCDD::Context`
|
1838
|
+
|
1839
|
+
In the further examples, I will use the `Divide` lambda to exemplify its usage.
|
1840
|
+
|
1841
|
+
```ruby
|
1842
|
+
Divide = lambda do |arg1, arg2|
|
1843
|
+
arg1.is_a?(::Numeric) or return BCDD::Context::Failure(:invalid_arg, err: 'arg1 must be numeric')
|
1844
|
+
arg2.is_a?(::Numeric) or return BCDD::Context::Failure(:invalid_arg, err: 'arg2 must be numeric')
|
1845
|
+
|
1846
|
+
return BCDD::Context::Failure(:division_by_zero, err: 'arg2 must not be zero') if arg2.zero?
|
1847
|
+
|
1848
|
+
BCDD::Context::Success(:division_completed, num: arg1 / arg2)
|
1849
|
+
end
|
1850
|
+
```
|
1851
|
+
|
1852
|
+
#### `Array`/`Find` patterns
|
1853
|
+
|
1854
|
+
```ruby
|
1855
|
+
case Divide.call(4, 2)
|
1856
|
+
in BCDD::Failure[:invalid_arg, {msg:}] then puts msg
|
1857
|
+
in BCDD::Failure[:division_by_zero, {msg:}] then puts msg
|
1858
|
+
in BCDD::Success[:division_completed, {num:}] then puts num
|
1859
|
+
end
|
1860
|
+
|
1861
|
+
# The code above will print: 2
|
1862
|
+
|
1863
|
+
case Divide.call(4, 0)
|
1864
|
+
in BCDD::Failure[:invalid_arg, {msg:}] then puts msg
|
1865
|
+
in BCDD::Failure[:division_by_zero, {msg:}] then puts msg
|
1866
|
+
in BCDD::Success[:division_completed, {num:}] then puts num
|
1867
|
+
end
|
1868
|
+
|
1869
|
+
# The code above will print: arg2 must not be zero
|
1870
|
+
```
|
1871
|
+
|
1872
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1873
|
+
|
1874
|
+
#### `Hash` patterns
|
1875
|
+
|
1876
|
+
If you don't provide the keys :type and :value, the pattern will match the result value.
|
1877
|
+
|
1878
|
+
```ruby
|
1879
|
+
case Divide.call(10, 2)
|
1880
|
+
in BCDD::Failure({msg:}) then puts msg
|
1881
|
+
in BCDD::Success({num:}) then puts num
|
1882
|
+
end
|
1883
|
+
```
|
1884
|
+
|
1885
|
+
```ruby
|
1886
|
+
case Divide.call(10, 2)
|
1887
|
+
in BCDD::Failure(type: :invalid_arg, value: {msg:}) then puts msg
|
1888
|
+
in BCDD::Failure(type: :division_by_zero, value: {msg:}) then puts msg
|
1889
|
+
in BCDD::Success(type: :division_completed, value: {num:}) then puts num
|
1890
|
+
end
|
1891
|
+
|
1892
|
+
# The code above will print: 5
|
1893
|
+
|
1894
|
+
case Divide.call('10', 2)
|
1895
|
+
in BCDD::Failure(type: :invalid_arg, value: {msg:}) then puts {msg:}
|
1896
|
+
in BCDD::Failure(type: :division_by_zero, value: {msg:}) then puts msg
|
1897
|
+
in BCDD::Success(type: :division_completed, value: {num:}) then puts num
|
1898
|
+
end
|
1899
|
+
|
1900
|
+
# The code above will print: arg1 must be numeric
|
1901
|
+
```
|
1902
|
+
|
1903
|
+
You can also use `BCDD::Context::Success` and `BCDD::Context::Failure` as patterns.
|
1904
|
+
|
1905
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1906
|
+
|
1907
|
+
### How to pattern match without the concept of success and failure
|
1908
|
+
|
1909
|
+
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.
|
1910
|
+
|
1911
|
+
```ruby
|
1912
|
+
case Divide.call(10, 2)
|
1913
|
+
in BCDD::Context(:invalid_arg, {msg:}) then puts msg
|
1914
|
+
in BCDD::Context(:division_by_zero, {msg:}) then puts msg
|
1915
|
+
in BCDD::Context(:division_completed, {num:}) then puts num
|
1916
|
+
end
|
1917
|
+
|
1918
|
+
case Divide.call(10, 2)
|
1919
|
+
in BCDD::Result(:invalid_arg, msg) then puts msg
|
1920
|
+
in BCDD::Result(:division_by_zero, msg) then puts msg
|
1921
|
+
in BCDD::Result(:division_completed, num) then puts num
|
1922
|
+
end
|
1923
|
+
```
|
1924
|
+
|
1925
|
+
The `BCDD::Result` will also work with the `BCDD::Context`, but the opposite won't.
|
1926
|
+
|
1927
|
+
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1928
|
+
|
1929
|
+
## `BCDD::Result.event_logs`
|
1930
|
+
|
1931
|
+
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.
|
1932
|
+
|
1933
|
+
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
1934
|
|
1797
1935
|
```ruby
|
1798
1936
|
class Division
|
1799
1937
|
include BCDD::Result.mixin(config: { addon: { continue: true } })
|
1800
1938
|
|
1801
1939
|
def call(arg1, arg2)
|
1802
|
-
BCDD::Result.
|
1940
|
+
BCDD::Result.event_logs(name: 'Division', desc: 'divide two numbers') do
|
1803
1941
|
Given([arg1, arg2])
|
1804
1942
|
.and_then(:require_numbers)
|
1805
1943
|
.and_then(:check_for_zeros)
|
@@ -1835,7 +1973,7 @@ module SumDivisionsByTwo
|
|
1835
1973
|
extend self, BCDD::Result.mixin
|
1836
1974
|
|
1837
1975
|
def call(*numbers)
|
1838
|
-
BCDD::Result.
|
1976
|
+
BCDD::Result.event_logs(name: 'SumDivisionsByTwo') do
|
1839
1977
|
divisions = numbers.map { |number| Division.new.call(number, 2) }
|
1840
1978
|
|
1841
1979
|
if divisions.any?(&:failure?)
|
@@ -1854,14 +1992,17 @@ Let's see the result of the `SumDivisionsByTwo` call:
|
|
1854
1992
|
result = SumDivisionsByTwo.call(20, 10)
|
1855
1993
|
# => #<BCDD::Result::Success type=:sum value=15>
|
1856
1994
|
|
1857
|
-
result.
|
1995
|
+
result.event_logs
|
1858
1996
|
{
|
1859
1997
|
:version => 1,
|
1860
1998
|
:metadata => {
|
1861
1999
|
:duration => 0, # milliseconds
|
1862
2000
|
:trace_id => nil, # can be set through configuration
|
1863
|
-
:
|
1864
|
-
|
2001
|
+
:ids => {
|
2002
|
+
:tree => [0, [[1, []], [2, []]]],
|
2003
|
+
:matrix => { 0 => [0, 0], 1 => [1, 1], 2 => [2, 1]},
|
2004
|
+
:level_parent => { 0 => [0, 0], 1 => [1, 0], 2 => [1, 0]}
|
2005
|
+
}
|
1865
2006
|
},
|
1866
2007
|
:records=> [
|
1867
2008
|
{
|
@@ -1942,18 +2083,21 @@ result.transitions
|
|
1942
2083
|
|
1943
2084
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
1944
2085
|
|
1945
|
-
### `
|
1946
|
-
|
1947
|
-
The `:ids_matrix`. It is a simplification of the `:ids_tree` property (a graph/tree representation of the transitions ids).
|
2086
|
+
### `metadata: {ids:}`
|
1948
2087
|
|
1949
|
-
The
|
2088
|
+
The `:ids` metadata property is a hash with three properties:
|
2089
|
+
- `:tree`, a graph/tree representation of the id of each `event_logs` block.
|
2090
|
+
- `:level_parent`, a hash with the level (depth) of each block and its parent id.
|
2091
|
+
- `:matrix`, a matrix representation of the event logs ids. It is a simplification of the `:tree` property.
|
1950
2092
|
|
1951
|
-
Use these data structures to build your own visualization
|
2093
|
+
Use these data structures to build your own visualization.
|
1952
2094
|
|
1953
|
-
> Check out [
|
2095
|
+
> 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
2096
|
|
1955
2097
|
```ruby
|
1956
|
-
#
|
2098
|
+
# tree:
|
2099
|
+
# A graph representation (array of arrays) of the each event logs block id.
|
2100
|
+
#
|
1957
2101
|
0 # [0, [
|
1958
2102
|
|- 1 # [1, [[2, []]]],
|
1959
2103
|
| |- 2 # [3, []],
|
@@ -1964,7 +2108,24 @@ Use these data structures to build your own visualization of the transitions.
|
|
1964
2108
|
| |- 7 # [8, []]
|
1965
2109
|
|- 8 # ]]
|
1966
2110
|
|
1967
|
-
#
|
2111
|
+
# level_parent:
|
2112
|
+
# The event logs ids are the keys, and the level (depth) and parent id the values.
|
2113
|
+
# {
|
2114
|
+
0 # 0 => [0, 0],
|
2115
|
+
|- 1 # 1 => [1, 0],
|
2116
|
+
| |- 2 # 2 => [2, 1],
|
2117
|
+
|- 3 # 3 => [1, 0],
|
2118
|
+
|- 4 # 4 => [1, 0],
|
2119
|
+
| |- 5 # 5 => [2, 4],
|
2120
|
+
| |- 6 # 6 => [2, 4],
|
2121
|
+
| |- 7 # 7 => [3, 6],
|
2122
|
+
|- 8 # 8 => [1, 0]
|
2123
|
+
# }
|
2124
|
+
|
2125
|
+
# matrix:
|
2126
|
+
# The rows are the direct blocks from the root block,
|
2127
|
+
# and the columns are the nested blocks from the direct ones.
|
2128
|
+
# {
|
1968
2129
|
0 | 1 | 2 | 3 | 4 # 0 => [0, 0],
|
1969
2130
|
- | - | - | - | - # 1 => [1, 1],
|
1970
2131
|
0 | | | | # 2 => [1, 2],
|
@@ -1983,78 +2144,84 @@ Use these data structures to build your own visualization of the transitions.
|
|
1983
2144
|
|
1984
2145
|
#### Turning on/off
|
1985
2146
|
|
1986
|
-
You can use `BCDD::Result.config.feature.disable!(
|
2147
|
+
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
2148
|
|
1988
2149
|
```ruby
|
1989
2150
|
BCDD::Result.configuration do |config|
|
1990
|
-
config.feature.disable!(
|
2151
|
+
config.feature.disable!(event_logs)
|
1991
2152
|
end
|
1992
2153
|
|
1993
2154
|
result = SumDivisionsByTwo.call(20, 10)
|
1994
2155
|
# => #<BCDD::Result::Success type=:sum value=15>
|
1995
2156
|
|
1996
|
-
result.
|
1997
|
-
|
1998
|
-
|
2157
|
+
result.event_logs
|
2158
|
+
{
|
2159
|
+
:version=>1,
|
2160
|
+
:records=>[],
|
2161
|
+
:metadata=>{
|
2162
|
+
:duration=>0,
|
2163
|
+
:ids=>{:tree=>[], :matrix=>{}, :level_parent=>{}}, :trace_id=>nil
|
2164
|
+
}
|
2165
|
+
}
|
1999
2166
|
```
|
2000
2167
|
|
2001
2168
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
2002
2169
|
|
2003
2170
|
#### Setting a `trace_id` fetcher
|
2004
2171
|
|
2005
|
-
You can define a lambda (arity 0) to fetch the trace_id. This lambda will be called before the first
|
2172
|
+
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
2173
|
|
2007
2174
|
Use to correlate different or the same operation (executed multiple times).
|
2008
2175
|
|
2009
2176
|
```ruby
|
2010
|
-
BCDD::Result.config.
|
2177
|
+
BCDD::Result.config.event_logs.trace_id = -> { Thread.current[:bcdd_result_event_logs_trace_id] }
|
2011
2178
|
```
|
2012
2179
|
|
2013
2180
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
2014
2181
|
|
2015
2182
|
#### Setting a `listener`
|
2016
2183
|
|
2017
|
-
You can define a listener to be called during the
|
2184
|
+
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
2185
|
|
2019
|
-
Use it to build your additional logic on top of the
|
2020
|
-
- Log the
|
2021
|
-
- Perform
|
2022
|
-
- Instrument the
|
2023
|
-
- Build a visualization
|
2186
|
+
Use it to build your additional logic on top of the tracking. Examples:
|
2187
|
+
- Log the event logs.
|
2188
|
+
- Perform the tracing.
|
2189
|
+
- Instrument the event logs (measure/report).
|
2190
|
+
- Build a visualization (Diagrams, using the `records:` + `metadata: {ids:}` properties).
|
2024
2191
|
|
2025
|
-
After implementing your listener, you can set it to the `BCDD::Result.config.
|
2192
|
+
After implementing your listener, you can set it to the `BCDD::Result.config.event_logs.listener=`:
|
2026
2193
|
|
2027
2194
|
```ruby
|
2028
|
-
BCDD::Result.config.
|
2195
|
+
BCDD::Result.config.event_logs.listener = MyEventLogsListener
|
2029
2196
|
```
|
2030
2197
|
|
2031
2198
|
See the example below to understand how to implement one:
|
2032
2199
|
|
2033
2200
|
```ruby
|
2034
|
-
class
|
2035
|
-
include BCDD::Result::
|
2201
|
+
class MyEventLogsListener
|
2202
|
+
include BCDD::Result::EventLogs::Listener
|
2036
2203
|
|
2037
|
-
# A listener will be initialized before the first
|
2204
|
+
# A listener will be initialized before the first event logs block, and it is discarded after the last one.
|
2038
2205
|
def initialize
|
2039
2206
|
end
|
2040
2207
|
|
2041
|
-
# This method will be called before each
|
2042
|
-
# The parent
|
2208
|
+
# This method will be called before each event logs block.
|
2209
|
+
# The parent block will be called first in the case of nested ones.
|
2043
2210
|
#
|
2044
2211
|
# @param scope: {:id=>1, :name=>"SomeOperation", :desc=>"Optional description"}
|
2045
2212
|
def on_start(scope:)
|
2046
2213
|
end
|
2047
2214
|
|
2048
|
-
# This method will wrap all the
|
2049
|
-
# It can be used to perform an instrumentation (measure/report)
|
2215
|
+
# This method will wrap all the event logs in the same block.
|
2216
|
+
# It can be used to perform an instrumentation (measure/report).
|
2050
2217
|
#
|
2051
2218
|
# @param scope: {:id=>1, :name=>"SomeOperation", :desc=>"Optional description"}
|
2052
|
-
def
|
2219
|
+
def around_event_logs(scope:)
|
2053
2220
|
yield
|
2054
2221
|
end
|
2055
2222
|
|
2056
2223
|
# This method will wrap each and_then call.
|
2057
|
-
# It can be used to perform an instrumentation
|
2224
|
+
# It can be used to perform an instrumentation of the and_then calls.
|
2058
2225
|
#
|
2059
2226
|
# @param scope: {:id=>1, :name=>"SomeOperation", :desc=>"Optional description"}
|
2060
2227
|
# @param and_then:
|
@@ -2078,29 +2245,32 @@ class MyTransitionsListener
|
|
2078
2245
|
def on_record(record:)
|
2079
2246
|
end
|
2080
2247
|
|
2081
|
-
# This method will be called at the end of the
|
2248
|
+
# This method will be called at the end of the event logs tracking.
|
2082
2249
|
#
|
2083
|
-
# @param
|
2250
|
+
# @param event_logs:
|
2084
2251
|
# {
|
2085
2252
|
# :version => 1,
|
2086
2253
|
# :metadata => {
|
2087
2254
|
# :duration => 0,
|
2088
2255
|
# :trace_id => nil,
|
2089
|
-
# :
|
2090
|
-
#
|
2256
|
+
# :ids => {
|
2257
|
+
# :tree => [0, [[1, []], [2, []]]],
|
2258
|
+
# :matrix => { 0 => [0, 0], 1 => [1, 1], 2 => [2, 1]},
|
2259
|
+
# :level_parent => { 0 => [0, 0], 1 => [1, 0], 2 => [1, 0]}
|
2260
|
+
# }
|
2091
2261
|
# },
|
2092
2262
|
# :records => [
|
2093
2263
|
# # ...
|
2094
2264
|
# ]
|
2095
2265
|
# }
|
2096
|
-
def on_finish(
|
2266
|
+
def on_finish(event_logs:)
|
2097
2267
|
end
|
2098
2268
|
|
2099
|
-
# This method will be called when an exception is raised during the
|
2269
|
+
# This method will be called when an exception is raised during the event logs tracking.
|
2100
2270
|
#
|
2101
2271
|
# @param exception: Exception
|
2102
|
-
# @param
|
2103
|
-
def before_interruption(exception:,
|
2272
|
+
# @param event_logs: Hash
|
2273
|
+
def before_interruption(exception:, event_logs:)
|
2104
2274
|
end
|
2105
2275
|
end
|
2106
2276
|
```
|
@@ -2109,15 +2279,15 @@ end
|
|
2109
2279
|
|
2110
2280
|
#### Setting multiple `listeners`
|
2111
2281
|
|
2112
|
-
You can use `BCDD::Result::
|
2282
|
+
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
2283
|
|
2114
|
-
**Attention:** It only allows one listener to handle `around_and_then` and another `
|
2284
|
+
**Attention:** It only allows one listener to handle `around_and_then` and another `around_event_logs` records.
|
2115
2285
|
|
2116
|
-
> The example below defines different listeners to handle `around_and_then` and `
|
2286
|
+
> 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
2287
|
|
2118
2288
|
```ruby
|
2119
2289
|
class AroundAndThenListener
|
2120
|
-
include BCDD::Result::
|
2290
|
+
include BCDD::Result::EventLogs::Listener
|
2121
2291
|
|
2122
2292
|
# It must be a static/singleton method.
|
2123
2293
|
def self.around_and_then?
|
@@ -2129,21 +2299,21 @@ class AroundAndThenListener
|
|
2129
2299
|
end
|
2130
2300
|
end
|
2131
2301
|
|
2132
|
-
class
|
2133
|
-
include BCDD::Result::
|
2302
|
+
class AroundEventLogsListener
|
2303
|
+
include BCDD::Result::EventLogs::Listener
|
2134
2304
|
|
2135
2305
|
# It must be a static/singleton method.
|
2136
|
-
def self.
|
2306
|
+
def self.around_event_logs?
|
2137
2307
|
true
|
2138
2308
|
end
|
2139
2309
|
|
2140
|
-
def
|
2310
|
+
def around_event_logs(scope:)
|
2141
2311
|
#...
|
2142
2312
|
end
|
2143
2313
|
end
|
2144
2314
|
|
2145
|
-
class
|
2146
|
-
include BCDD::Result::
|
2315
|
+
class MyEventLogsListener
|
2316
|
+
include BCDD::Result::EventLogs::Listener
|
2147
2317
|
end
|
2148
2318
|
```
|
2149
2319
|
|
@@ -2151,20 +2321,20 @@ How to use it:
|
|
2151
2321
|
|
2152
2322
|
```ruby
|
2153
2323
|
# The listeners will be called in the order they were added.
|
2154
|
-
BCDD::Result.config.
|
2155
|
-
|
2324
|
+
BCDD::Result.config.event_logs.listener = BCDD::Result::EventLogs::Listeners[
|
2325
|
+
MyEventLogsListener,
|
2156
2326
|
AroundAndThenListener,
|
2157
|
-
|
2327
|
+
AroundEventLogsListener
|
2158
2328
|
]
|
2159
2329
|
```
|
2160
2330
|
|
2161
|
-
> Check out [this example](examples/multiple_listeners) to see a listener to print the
|
2331
|
+
> 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
2332
|
|
2163
2333
|
<p align="right"><a href="#-bcddresult">⬆️ back to top</a></p>
|
2164
2334
|
|
2165
2335
|
## `BCDD::Result.configuration`
|
2166
2336
|
|
2167
|
-
The `BCDD::Result.configuration` allows you to configure default behaviors for `BCDD::Result` and `BCDD::
|
2337
|
+
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
2338
|
|
2169
2339
|
> 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
2340
|
|
@@ -2186,13 +2356,13 @@ Let's see what each configuration in the example above does:
|
|
2186
2356
|
|
2187
2357
|
### `config.addon.enable!(:given, :continue)` <!-- omit in toc -->
|
2188
2358
|
|
2189
|
-
This configuration enables the `Continue()` method for `BCDD::Result.mixin`, `BCDD::
|
2359
|
+
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
2360
|
|
2191
2361
|
It is also enabling the `Given()` which is already enabled by default. Link to documentation: [(1)](#add-ons) [(2)](#mixin-add-ons).
|
2192
2362
|
|
2193
2363
|
### `config.constant_alias.enable!('Result', 'BCDD::Context')` <!-- omit in toc -->
|
2194
2364
|
|
2195
|
-
This configuration make `Result` a constant alias for `BCDD::Result`, and `BCDD::Context` a constant alias for `BCDD::
|
2365
|
+
This configuration make `Result` a constant alias for `BCDD::Result`, and `BCDD::Context` a constant alias for `BCDD::Context`.
|
2196
2366
|
|
2197
2367
|
Link to documentations:
|
2198
2368
|
- [Result alias](#bcddresult-versus-result)
|
@@ -2200,11 +2370,11 @@ Link to documentations:
|
|
2200
2370
|
|
2201
2371
|
### `config.pattern_matching.disable!(:nil_as_valid_value_checking)` <!-- omit in toc -->
|
2202
2372
|
|
2203
|
-
This configuration disables the `nil_as_valid_value_checking` for `BCDD::Result` and `BCDD::
|
2373
|
+
This configuration disables the `nil_as_valid_value_checking` for `BCDD::Result` and `BCDD::Context`. Link to [documentation](#pattern-matching-support).
|
2204
2374
|
|
2205
2375
|
### `config.feature.disable!(:expectations)` <!-- omit in toc -->
|
2206
2376
|
|
2207
|
-
This configuration turns off the expectations for `BCDD::Result` and `BCDD::
|
2377
|
+
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
2378
|
|
2209
2379
|
PS: I'm using `::Rails.env.production?` to check the environment, but you can use any logic you want.
|
2210
2380
|
|
@@ -2226,18 +2396,18 @@ BCDD::Result.config.addon.options
|
|
2226
2396
|
# :enabled=>false,
|
2227
2397
|
# :affects=>[
|
2228
2398
|
# "BCDD::Result.mixin",
|
2229
|
-
# "BCDD::
|
2399
|
+
# "BCDD::Context.mixin",
|
2230
2400
|
# "BCDD::Result::Expectations.mixin",
|
2231
|
-
# "BCDD::
|
2401
|
+
# "BCDD::Context::Expectations.mixin"
|
2232
2402
|
# ]
|
2233
2403
|
# },
|
2234
2404
|
# :given=>{
|
2235
2405
|
# :enabled=>true,
|
2236
2406
|
# :affects=>[
|
2237
2407
|
# "BCDD::Result.mixin",
|
2238
|
-
# "BCDD::
|
2408
|
+
# "BCDD::Context.mixin",
|
2239
2409
|
# "BCDD::Result::Expectations.mixin",
|
2240
|
-
# "BCDD::
|
2410
|
+
# "BCDD::Context::Expectations.mixin"
|
2241
2411
|
# ]
|
2242
2412
|
# }
|
2243
2413
|
# }
|
@@ -2269,7 +2439,7 @@ BCDD::Result.config.pattern_matching.options
|
|
2269
2439
|
# :enabled=>false,
|
2270
2440
|
# :affects=>[
|
2271
2441
|
# "BCDD::Result::Expectations,
|
2272
|
-
# "BCDD::
|
2442
|
+
# "BCDD::Context::Expectations"
|
2273
2443
|
# ]
|
2274
2444
|
# }
|
2275
2445
|
# }
|
@@ -2286,21 +2456,21 @@ BCDD::Result.config.feature.options
|
|
2286
2456
|
# :enabled=>true,
|
2287
2457
|
# :affects=>[
|
2288
2458
|
# "BCDD::Result::Expectations,
|
2289
|
-
# "BCDD::
|
2459
|
+
# "BCDD::Context::Expectations"
|
2290
2460
|
# ]
|
2291
2461
|
# },
|
2292
|
-
#
|
2462
|
+
# event_logs=>{
|
2293
2463
|
# :enabled=>true,
|
2294
2464
|
# :affects=>[
|
2295
2465
|
# "BCDD::Result",
|
2296
|
-
# "BCDD::
|
2466
|
+
# "BCDD::Context"
|
2297
2467
|
# ]
|
2298
2468
|
# },
|
2299
2469
|
# :and_then!=>{
|
2300
2470
|
# :enabled=>false,
|
2301
2471
|
# :affects=>[
|
2302
2472
|
# "BCDD::Result",
|
2303
|
-
# "BCDD::
|
2473
|
+
# "BCDD::Context"
|
2304
2474
|
# ]
|
2305
2475
|
# },
|
2306
2476
|
# }
|
@@ -2343,7 +2513,7 @@ class PlaceOrder < Micro::Case
|
|
2343
2513
|
end
|
2344
2514
|
```
|
2345
2515
|
|
2346
|
-
To facilitate migration for users accustomed to the above approaches, `bcdd-result` includes the `BCDD::Result#and_then!`/`BCDD::
|
2516
|
+
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
2517
|
|
2348
2518
|
```ruby
|
2349
2519
|
BCDD::Result.configure do |config|
|
@@ -2351,7 +2521,7 @@ BCDD::Result.configure do |config|
|
|
2351
2521
|
end
|
2352
2522
|
|
2353
2523
|
class PlaceOrder
|
2354
|
-
include BCDD::
|
2524
|
+
include BCDD::Context.mixin
|
2355
2525
|
|
2356
2526
|
def call(**input)
|
2357
2527
|
Given(input)
|
@@ -2383,11 +2553,11 @@ class PlaceOrder
|
|
2383
2553
|
end
|
2384
2554
|
```
|
2385
2555
|
|
2386
|
-
**In BCDD::
|
2556
|
+
**In BCDD::Context**
|
2387
2557
|
|
2388
2558
|
```ruby
|
2389
2559
|
class PlaceOrder
|
2390
|
-
include BCDD::
|
2560
|
+
include BCDD::Context.mixin
|
2391
2561
|
|
2392
2562
|
def call(logger:, **input)
|
2393
2563
|
Given(input)
|
@@ -2441,7 +2611,7 @@ Attention: to ensure the correct behavior, do not mix `#and_then` and `#and_then
|
|
2441
2611
|
|
2442
2612
|
#### Analysis: Why is `#and_then` the antidote/standard?
|
2443
2613
|
|
2444
|
-
The `BCDD::Result#and_then`/`BCDD::
|
2614
|
+
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
2615
|
|
2446
2616
|
- **Clarity:** The input/output relationship between the steps is apparent and highly understandable.
|
2447
2617
|
|
@@ -2451,7 +2621,7 @@ See this example to understand what your code should look like:
|
|
2451
2621
|
|
2452
2622
|
```ruby
|
2453
2623
|
class PlaceOrder
|
2454
|
-
include BCDD::
|
2624
|
+
include BCDD::Context.mixin(config: { addon: { continue: true } })
|
2455
2625
|
|
2456
2626
|
def call(**input)
|
2457
2627
|
Given(input)
|