bcdd-result 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a41105b84f902004736a171bd42c943923ee188d14fe6630bf379fb3e3ef158
4
- data.tar.gz: 7b879ee4c04c2b60e95d1c2af18e28c8e7f1edb475c0cdb8274c56f6a577198a
3
+ metadata.gz: 0a6bfbf4821c7674fd0af6dbd4e88d274cbf14595eff278ef40ebdaf5bf4490a
4
+ data.tar.gz: 9cc905974762089c3f3827ac40aa730040ad9e88176e8cae0275954bedd6e7a8
5
5
  SHA512:
6
- metadata.gz: 48e9dd67d1f69a4b9aae0bbf1166aab532ca2f8cdc8883bf303b5a41f702a77c8531c4ae2baa96bf256a828e9db0624d631c1259e0870c9ee22ecd65b4ed2ce9
7
- data.tar.gz: cb2a0e618b21a4bf98c61b0a4dabfac322efe4293eab18c84cf729df920c49bf47557dcf20ceb01c42ac7c4673fa6fb55c235a2e93f2555a2021cbbfb3f37b36
6
+ metadata.gz: 5446879d905a42e257b3dd5724b6dc53a455cf2ec8e3fd7a5f339f74216e8d87ce779912dd4124163a6af4a19052e558382e74e7b20a434420b8d2eae055ab37
7
+ data.tar.gz: c91588b64905a6d86b84dc04db128e42fb0c570cf1e973ccd76563b0126e0e30d31764e918aa93aa71b06115a95c984299d4a57c249391d366d8738469aa8ecb
data/.rubocop.yml CHANGED
@@ -20,7 +20,7 @@ Style/ClassAndModuleChildren:
20
20
 
21
21
  Naming/MethodName:
22
22
  Exclude:
23
- - lib/bcdd/resultable.rb
23
+ - lib/bcdd/result/mixin.rb
24
24
 
25
25
  Minitest/MultipleAssertions:
26
26
  Enabled: false
data/.rubocop_todo.yml CHANGED
@@ -1,19 +1,22 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2023-09-25 17:14:03 UTC using RuboCop version 1.56.3.
3
+ # on 2023-09-29 01:50:30 UTC using RuboCop version 1.56.3.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 9
9
+ # Offense count: 14
10
10
  # Configuration parameters: AllowedConstants.
11
11
  Style/Documentation:
12
12
  Exclude:
13
13
  - 'spec/**/*'
14
14
  - 'test/**/*'
15
15
  - 'lib/bcdd/result.rb'
16
+ - 'lib/bcdd/result/data.rb'
16
17
  - 'lib/bcdd/result/error.rb'
17
18
  - 'lib/bcdd/result/failure.rb'
19
+ - 'lib/bcdd/result/handler.rb'
20
+ - 'lib/bcdd/result/mixin.rb'
18
21
  - 'lib/bcdd/result/success.rb'
19
- - 'lib/bcdd/resultable.rb'
22
+ - 'lib/bcdd/result/type.rb'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.0] - 2023-09-28
4
+
5
+ ### Added
6
+
7
+ - Add `require 'result'` to define `Result` as an alias for `BCDD::Result`.
8
+
9
+ - Add support to pattern matching (Ruby 2.7+).
10
+
11
+ - Add `BCDD::Result#on_unknown` to execute a block if no other hook (`#on`, `#on_type`, `#on_failure`, `#on_success`) has been executed. Attention: always use it as the last hook.
12
+
13
+ - Add `BCDD::Result::Handler#unknown` to execute a block if no other handler (`#[]`, `#type`, `#failure`, `#success`) has been executed. Attention: always use it as the last handler.
14
+
15
+ ### Changed
16
+
17
+ - **(BREAKING)** Rename `BCDD::Resultable` to `BCDD::Result::Mixin`.
18
+
19
+ - **(BREAKING)** Change `BCDD::Result#data` to return a `BCDD::Result::Data` instead of the result value. This object exposes the result attributes (name, type, value) directly and as a hash (`to_h`/`to_hash`) and array (`to_a`/`to_ary`).
20
+
21
+ ### Removed
22
+
23
+ - **(BREAKING)** Remove `BCDD::Result#data_or`.
24
+
25
+ ## [0.3.0] - 2023-09-26
26
+
27
+ ### Added
28
+
29
+ - Add `BCDD::Result#handle`. This method allows defining blocks for each hook (type, failure, success), but instead of returning the result itself, it will return the output of the first match/block execution.
30
+
3
31
  ## [0.2.0] - 2023-09-26
4
32
 
5
33
  ### Added
data/README.md CHANGED
@@ -1,10 +1,13 @@
1
1
  <p align="center">
2
2
  <h1 align="center" id="-bcddresult">🔀 BCDD::Result</h1>
3
3
  <p align="center"><i>Empower Ruby apps with a pragmatic use of Railway Oriented Programming.</i></p>
4
- <br>
4
+ <p align="center">
5
+ <img src="https://img.shields.io/badge/ruby->%3D%202.7.0-ruby.svg?colorA=99004d&colorB=cc0066" alt="Ruby">
6
+ <img src="https://badge.fury.io/rb/bcdd-result.svg" alt="bcdd-result gem version" height="18">
7
+ </p>
5
8
  </p>
6
9
 
7
- A general-purpose result monad that allows you to create objects that represent a success (`BCDD::Result::Success`) or failure (`BCDD::Result::Failure`).
10
+ It's a general-purpose result monad that allows you to create objects representing a success (`BCDD::Result::Success`) or failure (`BCDD::Result::Failure`).
8
11
 
9
12
  **What problem does it solve?**
10
13
 
@@ -14,8 +17,10 @@ Furthermore, this abstraction exposes several features that will be useful to ma
14
17
 
15
18
  Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofit.com/rop/) pattern (superpower) in your code.
16
19
 
20
+ - [Ruby Version](#ruby-version)
17
21
  - [Installation](#installation)
18
22
  - [Usage](#usage)
23
+ - [`BCDD::Result` *versus* `Result`](#bcddresult-versus-result)
19
24
  - [Reference](#reference)
20
25
  - [Result Attributes](#result-attributes)
21
26
  - [Receiving types in `result.success?` or `result.failure?`](#receiving-types-in-resultsuccess-or-resultfailure)
@@ -24,34 +29,48 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
24
29
  - [`result.on_type`](#resulton_type)
25
30
  - [`result.on_success`](#resulton_success)
26
31
  - [`result.on_failure`](#resulton_failure)
32
+ - [`result.on_unknown`](#resulton_unknown)
33
+ - [`result.handle`](#resulthandle)
27
34
  - [Result Value](#result-value)
28
35
  - [`result.value_or`](#resultvalue_or)
29
- - [`result.data_or`](#resultdata_or)
36
+ - [Result Data](#result-data)
37
+ - [`result.data`](#resultdata)
30
38
  - [Railway Oriented Programming](#railway-oriented-programming)
31
39
  - [`result.and_then`](#resultand_then)
32
- - [`BCDD::Resultable`](#bcddresultable)
40
+ - [`BCDD::Result::Mixin`](#bcddresultmixin)
33
41
  - [Class example (instance methods)](#class-example-instance-methods)
34
42
  - [Module example (singleton methods)](#module-example-singleton-methods)
35
43
  - [Restrictions](#restrictions)
44
+ - [Pattern Matching](#pattern-matching)
45
+ - [`Array`/`Find` patterns](#arrayfind-patterns)
46
+ - [`Hash` patterns](#hash-patterns)
36
47
  - [About](#about)
37
48
  - [Development](#development)
38
49
  - [Contributing](#contributing)
39
50
  - [License](#license)
40
51
  - [Code of Conduct](#code-of-conduct)
41
52
 
53
+ ## Ruby Version
54
+
55
+ `>= 2.7.0`
56
+
42
57
  ## Installation
43
58
 
44
- Install the gem and add to the application's Gemfile by executing:
59
+ Add this line to your application's Gemfile:
45
60
 
46
- $ bundle add bcdd-result
61
+ ```ruby
62
+ gem 'bcdd-result', require: 'bcdd/result'
63
+ ```
64
+
65
+ And then execute:
66
+
67
+ $ bundle install
47
68
 
48
69
  If bundler is not being used to manage dependencies, install the gem by executing:
49
70
 
50
71
  $ gem install bcdd-result
51
72
 
52
- > **NOTE:** This gem is compatible with Ruby >= 2.7.0
53
-
54
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
73
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
55
74
 
56
75
  ## Usage
57
76
 
@@ -71,7 +90,23 @@ BCDD::Result::Success(:ok) #
71
90
  BCDD::Result::Failure(:err) #
72
91
  ```
73
92
 
74
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
93
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
94
+
95
+ #### `BCDD::Result` *versus* `Result`
96
+
97
+ The `BCDD::Result` is the main module of this gem. It contains all the features, constants, and methods you will use to create and manipulate results.
98
+
99
+ The `Result` is an alias of `BCDD::Result`. It was created to facilitate the use of this gem in the code. So, instead of requiring `BCDD::Result` everywhere, you can require `Result` and use it as an alias.
100
+
101
+ ```ruby
102
+ require 'result'
103
+
104
+ Result::Success(:ok) # <BCDD::Result::Success type=:ok value=nil>
105
+ ```
106
+
107
+ All the examples in this README that use `BCDD::Result` can also be used with `Result`.
108
+
109
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
75
110
 
76
111
  ## Reference
77
112
 
@@ -127,7 +162,7 @@ result.type # :no
127
162
  result.value # nil
128
163
  ```
129
164
 
130
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
165
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
131
166
 
132
167
  #### Receiving types in `result.success?` or `result.failure?`
133
168
 
@@ -153,7 +188,7 @@ result.failure?(:err) # true
153
188
  result.failure?(:error) # false
154
189
  ```
155
190
 
156
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
191
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
157
192
 
158
193
  ### Result Hooks
159
194
 
@@ -172,7 +207,7 @@ def divide(arg1, arg2)
172
207
  end
173
208
  ```
174
209
 
175
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
210
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
176
211
 
177
212
  #### `result.on`
178
213
 
@@ -210,7 +245,7 @@ result.object_id == output.object_id # true
210
245
 
211
246
  *PS: The `divide()` implementation is [here](#result-hooks).*
212
247
 
213
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
248
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
214
249
 
215
250
  #### `result.on_type`
216
251
 
@@ -239,7 +274,7 @@ divide(4, 4).on_failure { |error| puts error }
239
274
 
240
275
  *PS: The `divide()` implementation is [here](#result-hooks).*
241
276
 
242
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
277
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
243
278
 
244
279
  #### `result.on_failure`
245
280
 
@@ -261,7 +296,78 @@ divide(4, 0).on_failure(:invalid_arg) { |error| puts error }
261
296
 
262
297
  *PS: The `divide()` implementation is [here](#result-hooks).*
263
298
 
264
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
299
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
300
+
301
+ #### `result.on_unknown`
302
+
303
+ `BCDD::Result#on_unknown` will perform the block when no other hook (`#on`, `#on_type`, `#on_failure`, `#on_success`) has been executed.
304
+
305
+ Regardless of the block being executed, the return of the method will always be the result itself.
306
+
307
+ The result value will be exposed as the first argument of the block.
308
+
309
+ ```ruby
310
+ divide(4, 2)
311
+ .on(:invalid_arg) { |msg| puts msg }
312
+ .on(:division_by_zero) { |msg| puts msg }
313
+ .on_unknown { |value, type| puts [type, value].inspect }
314
+
315
+ # The code above will print '[:division_completed, 2]' and return the result itself.
316
+ ```
317
+
318
+ *PS: The `divide()` implementation is [here](#result-hooks).*
319
+
320
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
321
+
322
+ #### `result.handle`
323
+
324
+ This method will allow you to define blocks for each hook (type, failure, success), but instead of returning itself, it will return the output of the first match/block execution.
325
+
326
+ ```ruby
327
+ divide(4, 2).handle do |result|
328
+ result.success { |number| number }
329
+ result.failure(:invalid_arg) { |err| puts err }
330
+ result.type(:division_by_zero) { raise ZeroDivisionError }
331
+ result.unknown { raise NotImplementedError }
332
+ end
333
+
334
+ #or
335
+
336
+ divide(4, 2).handle do |on|
337
+ on.success { |number| number }
338
+ on.failure { |err| puts err }
339
+ on.unknown { raise NotImplementedError }
340
+ end
341
+
342
+ #or
343
+
344
+ divide(4, 2).handle do |on|
345
+ on.type(:invalid_arg) { |err| puts err }
346
+ on.type(:division_by_zero) { raise ZeroDivisionError }
347
+ on.type(:division_completed) { |number| number }
348
+ on.unknown { raise NotImplementedError }
349
+ end
350
+
351
+ # or
352
+
353
+ divide(4, 2).handle do |on|
354
+ on[:invalid_arg] { |err| puts err }
355
+ on[:division_by_zero] { raise ZeroDivisionError }
356
+ on[:division_completed] { |number| number }
357
+ on.unknown { raise NotImplementedError }
358
+ end
359
+
360
+ # The [] syntax 👆 is an alias of #type.
361
+ ```
362
+
363
+ **Notes:**
364
+ * You can define multiple types to be handled by the same hook/block
365
+ * If the type is missing, it will perform the block for any success or failure handler.
366
+ * The `#type` and `#[]` handlers will require at least one type/argument.
367
+
368
+ *PS: The `divide()` implementation is [here](#result-hooks).*
369
+
370
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
265
371
 
266
372
  ### Result Value
267
373
 
@@ -294,11 +400,50 @@ divide(100, 0).value_or { 0 } # 0
294
400
 
295
401
  *PS: The `divide()` implementation is [here](#result-hooks).*
296
402
 
297
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
403
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
404
+
405
+ ### Result Data
406
+
407
+ #### `result.data`
298
408
 
299
- #### `result.data_or`
409
+ The `BCDD::Result#data` exposes the result attributes (name, type, value) directly and as a hash (`to_h`/`to_hash`) and array (`to_a`/`to_ary`).
300
410
 
301
- `BCDD::Result#data_or` is an alias of `BCDD::Result#value_or`.
411
+ This is helpful if you need to access the result attributes generically or want to use Ruby features like splat (`*`) and double splat (`**`) operators.
412
+
413
+ See the examples below to understand how to use it.
414
+
415
+ ```ruby
416
+ result = BCDD::Result::Success(:ok, 1)
417
+
418
+ success_data = result.data # #<BCDD::Result::Data name=:success type=:ok value=1>
419
+
420
+ success_data.name # :success
421
+ success_data.type # :ok
422
+ success_data.value # 1
423
+
424
+ success_data.to_h # {:name=>:success, :type=>:ok, :value=>1}
425
+ success_data.to_a # [:success, :ok, 1]
426
+
427
+ name, type, value = success_data
428
+
429
+ [name, type, value] # [:success, :ok, 1]
430
+
431
+ def print_to_ary(name, type, value)
432
+ puts [name, type, value].inspect
433
+ end
434
+
435
+ def print_to_hash(name:, type:, value:)
436
+ puts [name, type, value].inspect
437
+ end
438
+
439
+ print_to_ary(*success_data) # [:success, :ok, 1]
440
+
441
+ print_to_hash(**success_data) # [:success, :ok, 1]
442
+ ```
443
+
444
+ > **NOTE:** The example above uses a success result, but the same is valid for a failure result.
445
+
446
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
302
447
 
303
448
  ### Railway Oriented Programming
304
449
 
@@ -360,9 +505,9 @@ Divide.call(2, 2)
360
505
  #<BCDD::Result::Success type=:division_completed data=1>
361
506
  ```
362
507
 
363
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
508
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
364
509
 
365
- #### `BCDD::Resultable`
510
+ #### `BCDD::Result::Mixin`
366
511
 
367
512
  It is a module that can be included/extended by any object. It adds two methods to the target object: `Success()` and `Failure()`.
368
513
 
@@ -374,7 +519,7 @@ And because of this, you can use the `#and_then` method to call methods from the
374
519
 
375
520
  ```ruby
376
521
  class Divide
377
- include BCDD::Resultable
522
+ include BCDD::Result::Mixin
378
523
 
379
524
  attr_reader :arg1, :arg2
380
525
 
@@ -422,7 +567,7 @@ Divide.new(4, '2').call #<BCDD::Result::Failure type=:invalid_arg value="arg2 mu
422
567
 
423
568
  ```ruby
424
569
  module Divide
425
- extend BCDD::Resultable
570
+ extend BCDD::Result::Mixin
426
571
  extend self
427
572
 
428
573
  def call(arg1, arg2)
@@ -462,17 +607,78 @@ Divide.call(4, '2') #<BCDD::Result::Failure type=:invalid_arg value="arg2 must b
462
607
 
463
608
  The unique condition for using the `#and_then` to call methods is that they must use the `Success()` and `Failure()` to produce their results.
464
609
 
465
- If you use `BCDD::Result::Subject()`/`BCDD::Result::Failure()`, or call another `BCDD::Resultable` object, the `#and_then` will raise an error because the subjects will be different.
610
+ If you use `BCDD::Result::Subject()`/`BCDD::Result::Failure()`, or use result from another `BCDD::Result::Mixin` instance, the `#and_then` will raise an error because the subjects will be different.
466
611
 
467
612
  > **Note**: You still can use the block syntax, but all the results must be produced by the subject's `Success()` and `Failure()` methods.
468
613
 
469
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
614
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
615
+
616
+ ### Pattern Matching
617
+
618
+ The `BCDD::Result` also provides support to pattern matching.
619
+
620
+ In the further examples, I will use the `Divide` lambda to exemplify its usage.
621
+
622
+ ```ruby
623
+ Divide = lambda do |arg1, arg2|
624
+ arg1.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg1 must be numeric')
625
+ arg2.is_a?(::Numeric) or return BCDD::Result::Failure(:invalid_arg, 'arg2 must be numeric')
626
+
627
+ return BCDD::Result::Failure(:division_by_zero, 'arg2 must not be zero') if arg2.zero?
628
+
629
+ BCDD::Result::Success(:division_completed, arg1 / arg2)
630
+ end
631
+ ```
632
+
633
+ #### `Array`/`Find` patterns
634
+
635
+ ```ruby
636
+ case Divide.call(4, 2)
637
+ in BCDD::Result::Failure[:invalid_arg, msg] then puts msg
638
+ in BCDD::Result::Failure[:division_by_zero, msg] then puts msg
639
+ in BCDD::Result::Success[:division_completed, value] then puts value
640
+ end
641
+
642
+ # The code above will print: 2
643
+
644
+ case Divide.call(4, 0)
645
+ in BCDD::Result::Failure[:invalid_arg, msg] then puts msg
646
+ in BCDD::Result::Failure[:division_by_zero, msg] then puts msg
647
+ in BCDD::Result::Success[:division_completed, value] then puts value
648
+ end
649
+
650
+ # The code above will print: arg2 must not be zero
651
+ ```
652
+
653
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
654
+
655
+ #### `Hash` patterns
656
+
657
+ ```ruby
658
+ case Divide.call(10, 2)
659
+ in { failure: { invalid_arg: msg } } then puts msg
660
+ in { failure: { division_by_zero: msg } } then puts msg
661
+ in { success: { division_completed: value } } then puts value
662
+ end
663
+
664
+ # The code above will print: 5
665
+
666
+ case Divide.call('10', 2)
667
+ in { failure: { invalid_arg: msg } } then puts msg
668
+ in { failure: { division_by_zero: msg } } then puts msg
669
+ in { success: { division_completed: value } } then puts value
670
+ end
671
+
672
+ # The code above will print: arg1 must be numeric
673
+ ```
674
+
675
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
470
676
 
471
677
  ## About
472
678
 
473
679
  [Rodrigo Serradura](https://github.com/serradura) created this project. He is the B/CDD process/method creator and has already made similar gems like the [u-case](https://github.com/serradura/u-case) and [kind](https://github.com/serradura/kind/blob/main/lib/kind/result.rb). This gem is a general-purpose abstraction/monad, but it also contains key features that serve as facilitators for adopting B/CDD in the code.
474
680
 
475
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
681
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
476
682
 
477
683
  ## Development
478
684
 
@@ -480,19 +686,19 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
480
686
 
481
687
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
482
688
 
483
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
689
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
484
690
 
485
691
  ## Contributing
486
692
 
487
693
  Bug reports and pull requests are welcome on GitHub at https://github.com/B-CDD/bcdd-result. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/B-CDD/bcdd-result/blob/master/CODE_OF_CONDUCT.md).
488
694
 
489
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
695
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
490
696
 
491
697
  ## License
492
698
 
493
699
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
494
700
 
495
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
701
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
496
702
 
497
703
  ## Code of Conduct
498
704
 
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result
4
+ class Data
5
+ attr_reader :name, :type, :value, :to_h, :to_a
6
+
7
+ def initialize(result)
8
+ @name = result.send(:name)
9
+ @type = result.type
10
+ @value = result.value
11
+
12
+ @to_h = { name: name, type: type, value: value }
13
+ @to_a = [name, type, value]
14
+ end
15
+
16
+ def inspect
17
+ format(
18
+ '#<%<class_name>s name=%<name>p type=%<type>p value=%<value>p>',
19
+ class_name: self.class.name, name: name, type: type, value: value
20
+ )
21
+ end
22
+
23
+ alias to_ary to_a
24
+ alias to_hash to_h
25
+ end
26
+
27
+ private_constant :Data
28
+ end
@@ -1,46 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class BCDD::Result
4
- class Error < ::StandardError
5
- def self.build(**_kargs)
6
- new
7
- end
3
+ class BCDD::Result::Error < StandardError
4
+ def self.build(**_kargs)
5
+ new
6
+ end
8
7
 
9
- class NotImplemented < self
10
- end
8
+ class NotImplemented < self
9
+ end
11
10
 
12
- class MissingTypeArgument < self
13
- def initialize(_arg = nil)
14
- super('A type (argument) is required to invoke the #on/#on_type method')
15
- end
11
+ class MissingTypeArgument < self
12
+ def initialize(_arg = nil)
13
+ super('A type (argument) is required to invoke the #on/#on_type method')
16
14
  end
15
+ end
17
16
 
18
- class UnexpectedOutcome < self
19
- def self.build(outcome:, origin:)
20
- message =
21
- "Unexpected outcome: #{outcome.inspect}. The #{origin} must return this object wrapped by " \
22
- 'BCDD::Result::Success or BCDD::Result::Failure'
17
+ class UnexpectedOutcome < self
18
+ def self.build(outcome:, origin:)
19
+ message =
20
+ "Unexpected outcome: #{outcome.inspect}. The #{origin} must return this object wrapped by " \
21
+ 'BCDD::Result::Success or BCDD::Result::Failure'
23
22
 
24
- new(message)
25
- end
23
+ new(message)
26
24
  end
25
+ end
27
26
 
28
- class WrongResultSubject < self
29
- def self.build(given_result:, expected_subject:)
30
- message =
31
- "You cannot call #and_then and return a result that does not belong to the subject!\n" \
32
- "Expected subject: #{expected_subject.inspect}\n" \
33
- "Given subject: #{given_result.send(:subject).inspect}\n" \
34
- "Given result: #{given_result.inspect}"
27
+ class WrongResultSubject < self
28
+ def self.build(given_result:, expected_subject:)
29
+ message =
30
+ "You cannot call #and_then and return a result that does not belong to the subject!\n" \
31
+ "Expected subject: #{expected_subject.inspect}\n" \
32
+ "Given subject: #{given_result.send(:subject).inspect}\n" \
33
+ "Given result: #{given_result.inspect}"
35
34
 
36
- new(message)
37
- end
35
+ new(message)
38
36
  end
37
+ end
39
38
 
40
- class WrongSubjectMethodArity < self
41
- def self.build(subject:, method:)
42
- new("#{subject.class}##{method.name} has unsupported arity (#{method.arity}). Expected 0 or 1.")
43
- end
39
+ class WrongSubjectMethodArity < self
40
+ def self.build(subject:, method:)
41
+ new("#{subject.class}##{method.name} has unsupported arity (#{method.arity}). Expected 0 or 1.")
44
42
  end
45
43
  end
46
44
  end
@@ -14,7 +14,11 @@ class BCDD::Result
14
14
  yield
15
15
  end
16
16
 
17
- alias data_or value_or
17
+ private
18
+
19
+ def name
20
+ :failure
21
+ end
18
22
  end
19
23
 
20
24
  def self.Failure(type, value = nil)
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result
4
+ class Handler
5
+ UNDEFINED = ::Object.new
6
+
7
+ def initialize(result)
8
+ @outcome = UNDEFINED
9
+
10
+ @_type = result._type
11
+ @result = result
12
+ end
13
+
14
+ def [](*types, &block)
15
+ raise Error::MissingTypeArgument if types.empty?
16
+
17
+ self.outcome = block if _type.in?(types, allow_empty: false)
18
+ end
19
+
20
+ def failure(*types, &block)
21
+ self.outcome = block if result.failure? && _type.in?(types, allow_empty: true)
22
+ end
23
+
24
+ def success(*types, &block)
25
+ self.outcome = block if result.success? && _type.in?(types, allow_empty: true)
26
+ end
27
+
28
+ def unknown(&block)
29
+ self.outcome = block unless outcome?
30
+ end
31
+
32
+ alias type []
33
+
34
+ private
35
+
36
+ attr_reader :_type, :result
37
+
38
+ def outcome?
39
+ @outcome != UNDEFINED
40
+ end
41
+
42
+ def outcome=(block)
43
+ @outcome = block.call(result.value, result.type) unless outcome?
44
+ end
45
+
46
+ def outcome
47
+ @outcome if outcome?
48
+ end
49
+ end
50
+
51
+ private_constant :Handler
52
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result
4
+ module Mixin
5
+ def Success(type, value = nil)
6
+ Success.new(type: type, value: value, subject: self)
7
+ end
8
+
9
+ def Failure(type, value = nil)
10
+ Failure.new(type: type, value: value, subject: self)
11
+ end
12
+ end
13
+ end
@@ -14,7 +14,11 @@ class BCDD::Result
14
14
  value
15
15
  end
16
16
 
17
- alias data_or value_or
17
+ private
18
+
19
+ def name
20
+ :success
21
+ end
18
22
  end
19
23
 
20
24
  def self.Success(type, value = nil)
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result
4
+ class Type
5
+ attr_reader :to_sym
6
+
7
+ def initialize(type)
8
+ @to_sym = type.to_sym
9
+ end
10
+
11
+ def in?(types, allow_empty: false)
12
+ (allow_empty && types.empty?) || types.any?(to_sym)
13
+ end
14
+ end
15
+
16
+ private_constant :Type
17
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module BCDD
4
4
  class Result
5
- VERSION = '0.2.0'
5
+ VERSION = '0.4.0'
6
6
  end
7
7
  end
data/lib/bcdd/result.rb CHANGED
@@ -2,20 +2,32 @@
2
2
 
3
3
  require_relative 'result/version'
4
4
  require_relative 'result/error'
5
+ require_relative 'result/type'
6
+ require_relative 'result/data'
7
+ require_relative 'result/handler'
5
8
  require_relative 'result/failure'
6
9
  require_relative 'result/success'
7
-
8
- require_relative 'resultable'
10
+ require_relative 'result/mixin'
9
11
 
10
12
  class BCDD::Result
11
- attr_reader :type, :value, :subject
13
+ attr_accessor :unknown
14
+
15
+ attr_reader :_type, :value, :subject
12
16
 
13
17
  protected :subject
14
18
 
19
+ private :unknown, :unknown=
20
+
15
21
  def initialize(type:, value:, subject: nil)
16
- @type = type.to_sym
22
+ @_type = Type.new(type)
17
23
  @value = value
18
24
  @subject = subject
25
+
26
+ self.unknown = true
27
+ end
28
+
29
+ def type
30
+ _type.to_sym
19
31
  end
20
32
 
21
33
  def success?(_type = nil)
@@ -30,31 +42,22 @@ class BCDD::Result
30
42
  raise Error::NotImplemented
31
43
  end
32
44
 
33
- def ==(other)
34
- self.class == other.class && type == other.type && value == other.value
35
- end
36
- alias eql? ==
37
-
38
- def hash
39
- [self.class, type, value].hash
40
- end
45
+ def on(*types, &block)
46
+ raise Error::MissingTypeArgument if types.empty?
41
47
 
42
- def inspect
43
- format('#<%<class_name>s type=%<type>p value=%<value>p>', class_name: self.class.name, type: type, value: value)
48
+ tap { known(block) if _type.in?(types, allow_empty: false) }
44
49
  end
45
50
 
46
- def on(*types)
47
- raise Error::MissingTypeArgument if types.empty?
48
-
49
- tap { yield(value, type) if expected_type?(types) }
51
+ def on_success(*types, &block)
52
+ tap { known(block) if success? && _type.in?(types, allow_empty: true) }
50
53
  end
51
54
 
52
- def on_success(*types)
53
- tap { yield(value, type) if success? && allowed_to_handle?(types) }
55
+ def on_failure(*types, &block)
56
+ tap { known(block) if failure? && _type.in?(types, allow_empty: true) }
54
57
  end
55
58
 
56
- def on_failure(*types)
57
- tap { yield(value, type) if failure? && allowed_to_handle?(types) }
59
+ def on_unknown
60
+ tap { yield(value, type) if unknown }
58
61
  end
59
62
 
60
63
  def and_then(method_name = nil)
@@ -67,18 +70,51 @@ class BCDD::Result
67
70
  ensure_result_object(result, origin: :block)
68
71
  end
69
72
 
70
- alias data value
71
- alias data_or value_or
73
+ def handle
74
+ handler = Handler.new(self)
75
+
76
+ yield handler
77
+
78
+ handler.send(:outcome)
79
+ end
80
+
81
+ def data
82
+ Data.new(self)
83
+ end
84
+
85
+ def ==(other)
86
+ self.class == other.class && type == other.type && value == other.value
87
+ end
88
+
89
+ def hash
90
+ [self.class, type, value].hash
91
+ end
92
+
93
+ def inspect
94
+ format('#<%<class_name>s type=%<type>p value=%<value>p>', class_name: self.class.name, type: type, value: value)
95
+ end
96
+
97
+ def deconstruct
98
+ [type, value]
99
+ end
100
+
101
+ def deconstruct_keys(_keys)
102
+ { name => { type => value } }
103
+ end
104
+
105
+ alias eql? ==
72
106
  alias on_type on
73
107
 
74
108
  private
75
109
 
76
- def expected_type?(types)
77
- types.any?(type)
110
+ def name
111
+ raise Error::NotImplemented
78
112
  end
79
113
 
80
- def allowed_to_handle?(types)
81
- types.empty? || expected_type?(types)
114
+ def known(block)
115
+ self.unknown = false
116
+
117
+ block.call(value, type)
82
118
  end
83
119
 
84
120
  def call_subject_method(method_name)
data/lib/result.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'bcdd/result'
4
+
5
+ Object.const_set(:Result, BCDD::Result)
data/sig/bcdd/result.rbs CHANGED
@@ -5,38 +5,46 @@ module BCDD
5
5
  end
6
6
 
7
7
  class BCDD::Result
8
- attr_reader type: Symbol
8
+ private attr_accessor unknown: bool
9
+
10
+ attr_reader _type: BCDD::Result::Type
9
11
  attr_reader value: untyped
10
12
  attr_reader subject: untyped
11
13
 
12
14
  def initialize: (type: Symbol, value: untyped, ?subject: untyped) -> void
13
15
 
16
+ def type: -> Symbol
17
+
14
18
  def success?: (?Symbol type) -> bool
15
19
  def failure?: (?Symbol type) -> bool
16
20
 
17
21
  def value_or: { () -> untyped } -> untyped
18
22
 
19
- def ==: (untyped) -> bool
20
- alias eql? ==
21
-
22
- def hash: -> Integer
23
-
24
- def inspect: -> String
25
-
26
23
  def on: (*Symbol) { (untyped, Symbol) -> void } -> BCDD::Result
27
24
  def on_success: (*Symbol) { (untyped, Symbol) -> void } -> BCDD::Result
28
25
  def on_failure: (*Symbol) { (untyped, Symbol) -> void } -> BCDD::Result
26
+ def on_unknown: () { (untyped, Symbol) -> void } -> BCDD::Result
29
27
 
30
28
  def and_then: (?Symbol method_name) { (untyped) -> untyped } -> BCDD::Result
31
29
 
32
- alias data value
33
- alias data_or value_or
30
+ def handle: { (BCDD::Result::Handler) -> void } -> untyped
31
+
32
+ def data: -> BCDD::Result::Data
33
+
34
+ def ==: (untyped) -> bool
35
+ def hash: -> Integer
36
+ def inspect: -> String
37
+
38
+ def deconstruct: -> [Symbol, [Symbol, untyped]]
39
+ def deconstruct_keys: (Array[Symbol]) -> Hash[Symbol, Hash[Symbol, untyped]]
40
+
41
+ alias eql? ==
34
42
  alias on_type on
35
43
 
36
44
  private
37
45
 
38
- def expected_type?: (Array[Symbol]) -> bool
39
- def allowed_to_handle?: (Array[Symbol]) -> bool
46
+ def name: -> Symbol
47
+ def known: (Proc) -> untyped
40
48
  def call_subject_method: (Symbol) -> BCDD::Result
41
49
  def ensure_result_object: (untyped, origin: Symbol) -> BCDD::Result
42
50
  end
@@ -55,14 +63,65 @@ class BCDD::Result
55
63
  def self.Failure: (Symbol type, ?untyped value) -> BCDD::Result::Failure
56
64
  end
57
65
 
58
- module BCDD
59
- module Resultable
66
+ class BCDD::Result
67
+ module Mixin
60
68
  def Success: (Symbol type, ?untyped value) -> BCDD::Result::Success
61
69
 
62
70
  def Failure: (Symbol type, ?untyped value) -> BCDD::Result::Failure
63
71
  end
64
72
  end
65
73
 
74
+ class BCDD::Result
75
+ class Handler
76
+ UNDEFINED: Object
77
+
78
+ def initialize: (BCDD::Result) -> void
79
+
80
+ def []: (*Symbol) { (untyped, Symbol) -> void } -> untyped
81
+ def failure: (*Symbol) { (untyped, Symbol) -> void } -> untyped
82
+ def success: (*Symbol) { (untyped, Symbol) -> void } -> untyped
83
+ def unknown: () { (untyped, Symbol) -> void } -> untyped
84
+
85
+ alias type []
86
+
87
+ private
88
+
89
+ attr_reader _type: BCDD::Result::Type
90
+ attr_reader result: BCDD::Result
91
+
92
+ def outcome?: -> bool
93
+ def outcome=: (Proc) -> void
94
+ def outcome: -> untyped
95
+ end
96
+ end
97
+
98
+ class BCDD::Result
99
+ class Type
100
+ attr_reader to_sym: Symbol
101
+
102
+ def initialize: (Symbol) -> void
103
+
104
+ def in?: (Array[Symbol], allow_empty: bool) -> bool
105
+ end
106
+ end
107
+
108
+ class BCDD::Result
109
+ class Data
110
+ attr_reader name: Symbol
111
+ attr_reader type: Symbol
112
+ attr_reader value: untyped
113
+ attr_reader to_h: Hash[Symbol, untyped]
114
+ attr_reader to_a: [Symbol, Symbol, untyped]
115
+
116
+ def initialize: (BCDD::Result) -> void
117
+
118
+ def inspect: -> String
119
+
120
+ alias to_ary to_a
121
+ alias to_hash to_h
122
+ end
123
+ end
124
+
66
125
  class BCDD::Result
67
126
  class Error < ::StandardError
68
127
  def self.build: (**untyped) -> BCDD::Result::Error
metadata CHANGED
@@ -1,17 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bcdd-result
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodrigo Serradura
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-09-26 00:00:00.000000000 Z
11
+ date: 2023-09-29 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: A general-purpose result monad that allows you to create objects that
14
- represent a success (BCDD::Result::Success) or failure (BCDD::Result::Failure).
13
+ description: |-
14
+ Empower Ruby apps with a pragmatic use of Railway Oriented Programming.
15
+
16
+ It's a general-purpose result monad that allows you to create objects representing a success (BCDD::Result::Success) or failure (BCDD::Result::Failure).
15
17
  email:
16
18
  - rodrigo.serradura@gmail.com
17
19
  executables: []
@@ -27,11 +29,15 @@ files:
27
29
  - Rakefile
28
30
  - Steepfile
29
31
  - lib/bcdd/result.rb
32
+ - lib/bcdd/result/data.rb
30
33
  - lib/bcdd/result/error.rb
31
34
  - lib/bcdd/result/failure.rb
35
+ - lib/bcdd/result/handler.rb
36
+ - lib/bcdd/result/mixin.rb
32
37
  - lib/bcdd/result/success.rb
38
+ - lib/bcdd/result/type.rb
33
39
  - lib/bcdd/result/version.rb
34
- - lib/bcdd/resultable.rb
40
+ - lib/result.rb
35
41
  - sig/bcdd/result.rbs
36
42
  homepage: https://github.com/b-cdd/result
37
43
  licenses:
@@ -60,5 +66,5 @@ requirements: []
60
66
  rubygems_version: 3.4.19
61
67
  signing_key:
62
68
  specification_version: 4
63
- summary: A result abstraction (monad based) for Ruby.
69
+ summary: A pragmatic result abstraction (monad based) for Ruby.
64
70
  test_files: []
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module BCDD::Resultable
4
- def Success(type, value = nil)
5
- BCDD::Result::Success.new(type: type, value: value, subject: self)
6
- end
7
-
8
- def Failure(type, value = nil)
9
- BCDD::Result::Failure.new(type: type, value: value, subject: self)
10
- end
11
- end