bcdd-result 0.3.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: 96c58d6a9132fbf42fc8f3c1d9ba683f0b6297e23b1f0536fc4bcb7efb5084df
4
- data.tar.gz: fcef45f3c5ddacc20ff5e9533012c7fbe5817878cef1ff528dabd8920e1cbccd
3
+ metadata.gz: 0a6bfbf4821c7674fd0af6dbd4e88d274cbf14595eff278ef40ebdaf5bf4490a
4
+ data.tar.gz: 9cc905974762089c3f3827ac40aa730040ad9e88176e8cae0275954bedd6e7a8
5
5
  SHA512:
6
- metadata.gz: d4cf048907571205c6cd0b90ca4b95c2480c34de79d082132e4f1cab1e809f4c6eec538d9d38b036ad313d9653420625b68a502272e73371b99b157f43315123
7
- data.tar.gz: 4662aaeb33a7eb770ab51529cc1f2c4e9d86844dec70dfcdc4f37674aab33d010d6cbe7932fc635f09979d47b4b631748a3d4791021e99108a011a93469a2d73
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,21 +1,22 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2023-09-27 00:47: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: 13
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'
18
19
  - 'lib/bcdd/result/handler.rb'
20
+ - 'lib/bcdd/result/mixin.rb'
19
21
  - 'lib/bcdd/result/success.rb'
20
22
  - 'lib/bcdd/result/type.rb'
21
- - 'lib/bcdd/resultable.rb'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
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
+
3
25
  ## [0.3.0] - 2023-09-26
4
26
 
5
27
  ### 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
 
@@ -17,6 +20,7 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
17
20
  - [Ruby Version](#ruby-version)
18
21
  - [Installation](#installation)
19
22
  - [Usage](#usage)
23
+ - [`BCDD::Result` *versus* `Result`](#bcddresult-versus-result)
20
24
  - [Reference](#reference)
21
25
  - [Result Attributes](#result-attributes)
22
26
  - [Receiving types in `result.success?` or `result.failure?`](#receiving-types-in-resultsuccess-or-resultfailure)
@@ -25,16 +29,21 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
25
29
  - [`result.on_type`](#resulton_type)
26
30
  - [`result.on_success`](#resulton_success)
27
31
  - [`result.on_failure`](#resulton_failure)
32
+ - [`result.on_unknown`](#resulton_unknown)
28
33
  - [`result.handle`](#resulthandle)
29
34
  - [Result Value](#result-value)
30
35
  - [`result.value_or`](#resultvalue_or)
31
- - [`result.data_or`](#resultdata_or)
36
+ - [Result Data](#result-data)
37
+ - [`result.data`](#resultdata)
32
38
  - [Railway Oriented Programming](#railway-oriented-programming)
33
39
  - [`result.and_then`](#resultand_then)
34
- - [`BCDD::Resultable`](#bcddresultable)
40
+ - [`BCDD::Result::Mixin`](#bcddresultmixin)
35
41
  - [Class example (instance methods)](#class-example-instance-methods)
36
42
  - [Module example (singleton methods)](#module-example-singleton-methods)
37
43
  - [Restrictions](#restrictions)
44
+ - [Pattern Matching](#pattern-matching)
45
+ - [`Array`/`Find` patterns](#arrayfind-patterns)
46
+ - [`Hash` patterns](#hash-patterns)
38
47
  - [About](#about)
39
48
  - [Development](#development)
40
49
  - [Contributing](#contributing)
@@ -61,7 +70,7 @@ If bundler is not being used to manage dependencies, install the gem by executin
61
70
 
62
71
  $ gem install bcdd-result
63
72
 
64
- <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>
65
74
 
66
75
  ## Usage
67
76
 
@@ -81,7 +90,23 @@ BCDD::Result::Success(:ok) #
81
90
  BCDD::Result::Failure(:err) #
82
91
  ```
83
92
 
84
- <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>
85
110
 
86
111
  ## Reference
87
112
 
@@ -137,7 +162,7 @@ result.type # :no
137
162
  result.value # nil
138
163
  ```
139
164
 
140
- <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>
141
166
 
142
167
  #### Receiving types in `result.success?` or `result.failure?`
143
168
 
@@ -163,7 +188,7 @@ result.failure?(:err) # true
163
188
  result.failure?(:error) # false
164
189
  ```
165
190
 
166
- <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>
167
192
 
168
193
  ### Result Hooks
169
194
 
@@ -182,7 +207,7 @@ def divide(arg1, arg2)
182
207
  end
183
208
  ```
184
209
 
185
- <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>
186
211
 
187
212
  #### `result.on`
188
213
 
@@ -220,7 +245,7 @@ result.object_id == output.object_id # true
220
245
 
221
246
  *PS: The `divide()` implementation is [here](#result-hooks).*
222
247
 
223
- <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>
224
249
 
225
250
  #### `result.on_type`
226
251
 
@@ -249,7 +274,7 @@ divide(4, 4).on_failure { |error| puts error }
249
274
 
250
275
  *PS: The `divide()` implementation is [here](#result-hooks).*
251
276
 
252
- <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>
253
278
 
254
279
  #### `result.on_failure`
255
280
 
@@ -271,7 +296,28 @@ divide(4, 0).on_failure(:invalid_arg) { |error| puts error }
271
296
 
272
297
  *PS: The `divide()` implementation is [here](#result-hooks).*
273
298
 
274
- <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>
275
321
 
276
322
  #### `result.handle`
277
323
 
@@ -282,6 +328,7 @@ divide(4, 2).handle do |result|
282
328
  result.success { |number| number }
283
329
  result.failure(:invalid_arg) { |err| puts err }
284
330
  result.type(:division_by_zero) { raise ZeroDivisionError }
331
+ result.unknown { raise NotImplementedError }
285
332
  end
286
333
 
287
334
  #or
@@ -289,6 +336,7 @@ end
289
336
  divide(4, 2).handle do |on|
290
337
  on.success { |number| number }
291
338
  on.failure { |err| puts err }
339
+ on.unknown { raise NotImplementedError }
292
340
  end
293
341
 
294
342
  #or
@@ -297,6 +345,7 @@ divide(4, 2).handle do |on|
297
345
  on.type(:invalid_arg) { |err| puts err }
298
346
  on.type(:division_by_zero) { raise ZeroDivisionError }
299
347
  on.type(:division_completed) { |number| number }
348
+ on.unknown { raise NotImplementedError }
300
349
  end
301
350
 
302
351
  # or
@@ -305,6 +354,7 @@ divide(4, 2).handle do |on|
305
354
  on[:invalid_arg] { |err| puts err }
306
355
  on[:division_by_zero] { raise ZeroDivisionError }
307
356
  on[:division_completed] { |number| number }
357
+ on.unknown { raise NotImplementedError }
308
358
  end
309
359
 
310
360
  # The [] syntax 👆 is an alias of #type.
@@ -317,7 +367,7 @@ end
317
367
 
318
368
  *PS: The `divide()` implementation is [here](#result-hooks).*
319
369
 
320
- <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
370
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
321
371
 
322
372
  ### Result Value
323
373
 
@@ -350,11 +400,50 @@ divide(100, 0).value_or { 0 } # 0
350
400
 
351
401
  *PS: The `divide()` implementation is [here](#result-hooks).*
352
402
 
353
- <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`
408
+
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`).
410
+
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]
354
440
 
355
- #### `result.data_or`
441
+ print_to_hash(**success_data) # [:success, :ok, 1]
442
+ ```
356
443
 
357
- `BCDD::Result#data_or` is an alias of `BCDD::Result#value_or`.
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>
358
447
 
359
448
  ### Railway Oriented Programming
360
449
 
@@ -416,9 +505,9 @@ Divide.call(2, 2)
416
505
  #<BCDD::Result::Success type=:division_completed data=1>
417
506
  ```
418
507
 
419
- <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>
420
509
 
421
- #### `BCDD::Resultable`
510
+ #### `BCDD::Result::Mixin`
422
511
 
423
512
  It is a module that can be included/extended by any object. It adds two methods to the target object: `Success()` and `Failure()`.
424
513
 
@@ -430,7 +519,7 @@ And because of this, you can use the `#and_then` method to call methods from the
430
519
 
431
520
  ```ruby
432
521
  class Divide
433
- include BCDD::Resultable
522
+ include BCDD::Result::Mixin
434
523
 
435
524
  attr_reader :arg1, :arg2
436
525
 
@@ -478,7 +567,7 @@ Divide.new(4, '2').call #<BCDD::Result::Failure type=:invalid_arg value="arg2 mu
478
567
 
479
568
  ```ruby
480
569
  module Divide
481
- extend BCDD::Resultable
570
+ extend BCDD::Result::Mixin
482
571
  extend self
483
572
 
484
573
  def call(arg1, arg2)
@@ -518,17 +607,78 @@ Divide.call(4, '2') #<BCDD::Result::Failure type=:invalid_arg value="arg2 must b
518
607
 
519
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.
520
609
 
521
- 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.
522
611
 
523
612
  > **Note**: You still can use the block syntax, but all the results must be produced by the subject's `Success()` and `Failure()` methods.
524
613
 
525
- <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>
526
676
 
527
677
  ## About
528
678
 
529
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.
530
680
 
531
- <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>
532
682
 
533
683
  ## Development
534
684
 
@@ -536,19 +686,19 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
536
686
 
537
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).
538
688
 
539
- <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>
540
690
 
541
691
  ## Contributing
542
692
 
543
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).
544
694
 
545
- <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>
546
696
 
547
697
  ## License
548
698
 
549
699
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
550
700
 
551
- <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>
552
702
 
553
703
  ## Code of Conduct
554
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)
@@ -25,6 +25,10 @@ class BCDD::Result
25
25
  self.outcome = block if result.success? && _type.in?(types, allow_empty: true)
26
26
  end
27
27
 
28
+ def unknown(&block)
29
+ self.outcome = block unless outcome?
30
+ end
31
+
28
32
  alias type []
29
33
 
30
34
  private
@@ -35,13 +39,13 @@ class BCDD::Result
35
39
  @outcome != UNDEFINED
36
40
  end
37
41
 
38
- def outcome
39
- @outcome if outcome?
40
- end
41
-
42
42
  def outcome=(block)
43
43
  @outcome = block.call(result.value, result.type) unless outcome?
44
44
  end
45
+
46
+ def outcome
47
+ @outcome if outcome?
48
+ end
45
49
  end
46
50
 
47
51
  private_constant :Handler
@@ -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)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module BCDD
4
4
  class Result
5
- VERSION = '0.3.0'
5
+ VERSION = '0.4.0'
6
6
  end
7
7
  end
data/lib/bcdd/result.rb CHANGED
@@ -3,21 +3,27 @@
3
3
  require_relative 'result/version'
4
4
  require_relative 'result/error'
5
5
  require_relative 'result/type'
6
+ require_relative 'result/data'
6
7
  require_relative 'result/handler'
7
8
  require_relative 'result/failure'
8
9
  require_relative 'result/success'
9
-
10
- require_relative 'resultable'
10
+ require_relative 'result/mixin'
11
11
 
12
12
  class BCDD::Result
13
+ attr_accessor :unknown
14
+
13
15
  attr_reader :_type, :value, :subject
14
16
 
15
17
  protected :subject
16
18
 
19
+ private :unknown, :unknown=
20
+
17
21
  def initialize(type:, value:, subject: nil)
18
22
  @_type = Type.new(type)
19
23
  @value = value
20
24
  @subject = subject
25
+
26
+ self.unknown = true
21
27
  end
22
28
 
23
29
  def type
@@ -36,31 +42,22 @@ class BCDD::Result
36
42
  raise Error::NotImplemented
37
43
  end
38
44
 
39
- def ==(other)
40
- self.class == other.class && type == other.type && value == other.value
41
- end
42
- alias eql? ==
43
-
44
- def hash
45
- [self.class, type, value].hash
46
- end
45
+ def on(*types, &block)
46
+ raise Error::MissingTypeArgument if types.empty?
47
47
 
48
- def inspect
49
- 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) }
50
49
  end
51
50
 
52
- def on(*types)
53
- raise Error::MissingTypeArgument if types.empty?
54
-
55
- tap { yield(value, type) if _type.in?(types, allow_empty: false) }
51
+ def on_success(*types, &block)
52
+ tap { known(block) if success? && _type.in?(types, allow_empty: true) }
56
53
  end
57
54
 
58
- def on_success(*types)
59
- tap { yield(value, type) if success? && _type.in?(types, allow_empty: true) }
55
+ def on_failure(*types, &block)
56
+ tap { known(block) if failure? && _type.in?(types, allow_empty: true) }
60
57
  end
61
58
 
62
- def on_failure(*types)
63
- tap { yield(value, type) if failure? && _type.in?(types, allow_empty: true) }
59
+ def on_unknown
60
+ tap { yield(value, type) if unknown }
64
61
  end
65
62
 
66
63
  def and_then(method_name = nil)
@@ -81,12 +78,45 @@ class BCDD::Result
81
78
  handler.send(:outcome)
82
79
  end
83
80
 
84
- alias data value
85
- alias data_or value_or
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? ==
86
106
  alias on_type on
87
107
 
88
108
  private
89
109
 
110
+ def name
111
+ raise Error::NotImplemented
112
+ end
113
+
114
+ def known(block)
115
+ self.unknown = false
116
+
117
+ block.call(value, type)
118
+ end
119
+
90
120
  def call_subject_method(method_name)
91
121
  method = subject.method(method_name)
92
122
 
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,6 +5,8 @@ module BCDD
5
5
  end
6
6
 
7
7
  class BCDD::Result
8
+ private attr_accessor unknown: bool
9
+
8
10
  attr_reader _type: BCDD::Result::Type
9
11
  attr_reader value: untyped
10
12
  attr_reader subject: untyped
@@ -18,27 +20,31 @@ class BCDD::Result
18
20
 
19
21
  def value_or: { () -> untyped } -> untyped
20
22
 
21
- def ==: (untyped) -> bool
22
- alias eql? ==
23
-
24
- def hash: -> Integer
25
-
26
- def inspect: -> String
27
-
28
23
  def on: (*Symbol) { (untyped, Symbol) -> void } -> BCDD::Result
29
24
  def on_success: (*Symbol) { (untyped, Symbol) -> void } -> BCDD::Result
30
25
  def on_failure: (*Symbol) { (untyped, Symbol) -> void } -> BCDD::Result
26
+ def on_unknown: () { (untyped, Symbol) -> void } -> BCDD::Result
31
27
 
32
28
  def and_then: (?Symbol method_name) { (untyped) -> untyped } -> BCDD::Result
33
29
 
34
30
  def handle: { (BCDD::Result::Handler) -> void } -> untyped
35
31
 
36
- alias data value
37
- alias data_or value_or
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? ==
38
42
  alias on_type on
39
43
 
40
44
  private
41
45
 
46
+ def name: -> Symbol
47
+ def known: (Proc) -> untyped
42
48
  def call_subject_method: (Symbol) -> BCDD::Result
43
49
  def ensure_result_object: (untyped, origin: Symbol) -> BCDD::Result
44
50
  end
@@ -57,6 +63,14 @@ class BCDD::Result
57
63
  def self.Failure: (Symbol type, ?untyped value) -> BCDD::Result::Failure
58
64
  end
59
65
 
66
+ class BCDD::Result
67
+ module Mixin
68
+ def Success: (Symbol type, ?untyped value) -> BCDD::Result::Success
69
+
70
+ def Failure: (Symbol type, ?untyped value) -> BCDD::Result::Failure
71
+ end
72
+ end
73
+
60
74
  class BCDD::Result
61
75
  class Handler
62
76
  UNDEFINED: Object
@@ -66,6 +80,7 @@ class BCDD::Result
66
80
  def []: (*Symbol) { (untyped, Symbol) -> void } -> untyped
67
81
  def failure: (*Symbol) { (untyped, Symbol) -> void } -> untyped
68
82
  def success: (*Symbol) { (untyped, Symbol) -> void } -> untyped
83
+ def unknown: () { (untyped, Symbol) -> void } -> untyped
69
84
 
70
85
  alias type []
71
86
 
@@ -75,18 +90,8 @@ class BCDD::Result
75
90
  attr_reader result: BCDD::Result
76
91
 
77
92
  def outcome?: -> bool
78
-
79
- def outcome: -> untyped
80
-
81
93
  def outcome=: (Proc) -> void
82
- end
83
- end
84
-
85
- module BCDD
86
- module Resultable
87
- def Success: (Symbol type, ?untyped value) -> BCDD::Result::Success
88
-
89
- def Failure: (Symbol type, ?untyped value) -> BCDD::Result::Failure
94
+ def outcome: -> untyped
90
95
  end
91
96
  end
92
97
 
@@ -100,6 +105,23 @@ class BCDD::Result
100
105
  end
101
106
  end
102
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
+
103
125
  class BCDD::Result
104
126
  class Error < ::StandardError
105
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.3.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-27 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,13 +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
32
35
  - lib/bcdd/result/handler.rb
36
+ - lib/bcdd/result/mixin.rb
33
37
  - lib/bcdd/result/success.rb
34
38
  - lib/bcdd/result/type.rb
35
39
  - lib/bcdd/result/version.rb
36
- - lib/bcdd/resultable.rb
40
+ - lib/result.rb
37
41
  - sig/bcdd/result.rbs
38
42
  homepage: https://github.com/b-cdd/result
39
43
  licenses:
@@ -62,5 +66,5 @@ requirements: []
62
66
  rubygems_version: 3.4.19
63
67
  signing_key:
64
68
  specification_version: 4
65
- summary: A result abstraction (monad based) for Ruby.
69
+ summary: A pragmatic result abstraction (monad based) for Ruby.
66
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