bcdd-result 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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