bcdd-result 0.1.0 → 0.2.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: e27dee9c053eeb9f6025b17e09eede49ca6cbc69cd9120947bad34b26a79859d
4
- data.tar.gz: c8c080d43a3eb46139f7bf5299c64346809d1c0df4bf0181ccb02ba3005d5ee0
3
+ metadata.gz: 8a41105b84f902004736a171bd42c943923ee188d14fe6630bf379fb3e3ef158
4
+ data.tar.gz: 7b879ee4c04c2b60e95d1c2af18e28c8e7f1edb475c0cdb8274c56f6a577198a
5
5
  SHA512:
6
- metadata.gz: 253e14ef154c2b863bb65139134a903b2a6fd035fb7a995158cf5ebab395d6ba2bd9f8e999c29305f2d2cd2f6e10db9923166a13d1f06e627df276c87c7a644d
7
- data.tar.gz: 7d62559447a5ee3cb665713645f1db7729428f9cf94bd349574758ea3f9bae361f65a218640b2d5bfeb138ef108c27dec426a44ec89dbc971620671b6eab945c
6
+ metadata.gz: 48e9dd67d1f69a4b9aae0bbf1166aab532ca2f8cdc8883bf303b5a41f702a77c8531c4ae2baa96bf256a828e9db0624d631c1259e0870c9ee22ecd65b4ed2ce9
7
+ data.tar.gz: cb2a0e618b21a4bf98c61b0a4dabfac322efe4293eab18c84cf729df920c49bf47557dcf20ceb01c42ac7c4673fa6fb55c235a2e93f2555a2021cbbfb3f37b36
data/.rubocop.yml CHANGED
@@ -18,5 +18,9 @@ Layout/ExtraSpacing:
18
18
  Style/ClassAndModuleChildren:
19
19
  Enabled: false
20
20
 
21
+ Naming/MethodName:
22
+ Exclude:
23
+ - lib/bcdd/resultable.rb
24
+
21
25
  Minitest/MultipleAssertions:
22
26
  Enabled: false
data/.rubocop_todo.yml CHANGED
@@ -1,19 +1,19 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2023-09-23 03:05:24 UTC using RuboCop version 1.56.3.
3
+ # on 2023-09-25 17:14:03 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: 8
9
+ # Offense count: 9
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/base.rb'
17
16
  - 'lib/bcdd/result/error.rb'
18
17
  - 'lib/bcdd/result/failure.rb'
19
18
  - 'lib/bcdd/result/success.rb'
19
+ - 'lib/bcdd/resultable.rb'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,99 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2023-09-26
4
+
5
+ ### Added
6
+
7
+ - Add `BCDD::Resultable`. This module can add `Success()` and `Failure()` in any object. The target object will be the subject of the result object produced by these methods.
8
+
9
+ **Classes (instance methods)**
10
+
11
+ ```ruby
12
+ class Divide
13
+ include BCDD::Resultable
14
+
15
+ attr_reader :arg1, :arg2
16
+
17
+ def initialize(arg1, arg2)
18
+ @arg1 = arg1
19
+ @arg2 = arg2
20
+ end
21
+
22
+ def call
23
+ validate_numbers
24
+ .and_then(:validate_non_zero)
25
+ .and_then(:divide)
26
+ end
27
+
28
+ private
29
+
30
+ def validate_numbers
31
+ arg1.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
32
+ arg2.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
33
+
34
+ Success(:ok, [arg1, arg2])
35
+ end
36
+
37
+ def validate_non_zero(numbers)
38
+ return Success(:ok, numbers) unless numbers.last.zero?
39
+
40
+ Failure(:division_by_zero, 'arg2 must not be zero')
41
+ end
42
+
43
+ def divide((number1, number2))
44
+ Success(:division_completed, number1 / number2)
45
+ end
46
+ end
47
+ ```
48
+
49
+ **Module (singleton methods)**
50
+
51
+ ```ruby
52
+ module Divide
53
+ extend BCDD::Resultable
54
+ extend self
55
+
56
+ def call(arg1, arg2)
57
+ validate_numbers(arg1, arg2)
58
+ .and_then(:validate_non_zero)
59
+ .and_then(:divide)
60
+ end
61
+
62
+ private
63
+
64
+ def validate_numbers(arg1, arg2)
65
+ arg1.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
66
+ arg2.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
67
+
68
+ Success(:ok, [arg1, arg2])
69
+ end
70
+
71
+ def validate_non_zero(numbers)
72
+ return Success(:ok, numbers) unless numbers.last.zero?
73
+
74
+ Failure(:division_by_zero, 'arg2 must not be zero')
75
+ end
76
+
77
+ def divide((number1, number2))
78
+ Success(:division_completed, number1 / number2)
79
+ end
80
+ end
81
+ ```
82
+
83
+ - Make the `BCDD::Result#initialize` enabled to receive a subject.
84
+
85
+ - Make the `BCDD::Result#and_then` method receive a method name (symbol) and perform it on the result subject (added by `BCDD::Resultable`). The called method must return a result; otherwise, an error (`BCDD::Result::Error::UnexpectedOutcome`) will be raised.
86
+
87
+ - Add `BCDD::Result::Error::UnexpectedOutcome` to represent an unexpected outcome.
88
+
89
+ - Add `BCDD::Result::Error::WrongResultSubject` to represent a wrong result subject. When using `BCDD::Resultable`, the result subject must be the same as the target object.
90
+
91
+ - Add `BCDD::Result::Error::WrongSubjectMethodArity` to represent a wrong subject method arity. Valid arities are 0 and 1.
92
+
93
+ ### Removed
94
+
95
+ - **(BREAKING)** Remove `BCDD::Result::Error::UnexpectedBlockOutcome`. It was replaced by `BCDD::Result::Error::UnexpectedOutcome`.
96
+
3
97
  ## [0.1.0] - 2023-09-25
4
98
 
5
99
  ### Added
data/README.md CHANGED
@@ -1,4 +1,8 @@
1
- # BCDD::Result <!-- omit in toc -->
1
+ <p align="center">
2
+ <h1 align="center" id="-bcddresult">🔀 BCDD::Result</h1>
3
+ <p align="center"><i>Empower Ruby apps with a pragmatic use of Railway Oriented Programming.</i></p>
4
+ <br>
5
+ </p>
2
6
 
3
7
  A general-purpose result monad that allows you to create objects that represent a success (`BCDD::Result::Success`) or failure (`BCDD::Result::Failure`).
4
8
 
@@ -6,7 +10,9 @@ A general-purpose result monad that allows you to create objects that represent
6
10
 
7
11
  It allows you to consistently represent the concept of success and failure throughout your codebase.
8
12
 
9
- Furthermore, this abstraction exposes several methods that will be useful to make the code flow react clearly and cleanly to the result represented by these objects.
13
+ Furthermore, this abstraction exposes several features that will be useful to make the application flow react cleanly and securely to the result represented by these objects.
14
+
15
+ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofit.com/rop/) pattern (superpower) in your code.
10
16
 
11
17
  - [Installation](#installation)
12
18
  - [Usage](#usage)
@@ -18,18 +24,21 @@ Furthermore, this abstraction exposes several methods that will be useful to mak
18
24
  - [`result.on_type`](#resulton_type)
19
25
  - [`result.on_success`](#resulton_success)
20
26
  - [`result.on_failure`](#resulton_failure)
21
- - [Result value](#result-value)
27
+ - [Result Value](#result-value)
22
28
  - [`result.value_or`](#resultvalue_or)
23
29
  - [`result.data_or`](#resultdata_or)
24
30
  - [Railway Oriented Programming](#railway-oriented-programming)
25
31
  - [`result.and_then`](#resultand_then)
32
+ - [`BCDD::Resultable`](#bcddresultable)
33
+ - [Class example (instance methods)](#class-example-instance-methods)
34
+ - [Module example (singleton methods)](#module-example-singleton-methods)
35
+ - [Restrictions](#restrictions)
26
36
  - [About](#about)
27
37
  - [Development](#development)
28
38
  - [Contributing](#contributing)
29
39
  - [License](#license)
30
40
  - [Code of Conduct](#code-of-conduct)
31
41
 
32
-
33
42
  ## Installation
34
43
 
35
44
  Install the gem and add to the application's Gemfile by executing:
@@ -40,6 +49,10 @@ If bundler is not being used to manage dependencies, install the gem by executin
40
49
 
41
50
  $ gem install bcdd-result
42
51
 
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>
55
+
43
56
  ## Usage
44
57
 
45
58
  To create a result, you must define a type (symbol) and its value (any kind of object). e.g.,
@@ -58,7 +71,7 @@ BCDD::Result::Success(:ok) #
58
71
  BCDD::Result::Failure(:err) #
59
72
  ```
60
73
 
61
- <p align="right">(<a href="#bcddresult-">⬆️ &nbsp;back to top</a>)</p>
74
+ <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
62
75
 
63
76
  ## Reference
64
77
 
@@ -114,7 +127,7 @@ result.type # :no
114
127
  result.value # nil
115
128
  ```
116
129
 
117
- <p align="right">(<a href="#bcddresult-">⬆️ &nbsp;back to top</a>)</p>
130
+ <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
118
131
 
119
132
  #### Receiving types in `result.success?` or `result.failure?`
120
133
 
@@ -140,7 +153,7 @@ result.failure?(:err) # true
140
153
  result.failure?(:error) # false
141
154
  ```
142
155
 
143
- <p align="right">(<a href="#bcddresult-">⬆️ &nbsp;back to top</a>)</p>
156
+ <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
144
157
 
145
158
  ### Result Hooks
146
159
 
@@ -159,6 +172,8 @@ def divide(arg1, arg2)
159
172
  end
160
173
  ```
161
174
 
175
+ <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
176
+
162
177
  #### `result.on`
163
178
 
164
179
  `BCDD::Result#on` will perform the block when the type matches the result type.
@@ -193,7 +208,9 @@ output =
193
208
  result.object_id == output.object_id # true
194
209
  ```
195
210
 
196
- <p align="right">(<a href="#bcddresult-">⬆️ &nbsp;back to top</a>)</p>
211
+ *PS: The `divide()` implementation is [here](#result-hooks).*
212
+
213
+ <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
197
214
 
198
215
  #### `result.on_type`
199
216
 
@@ -220,7 +237,9 @@ divide(4, 4).on_success(:ok) { |value| puts value }
220
237
  divide(4, 4).on_failure { |error| puts error }
221
238
  ```
222
239
 
223
- <p align="right">(<a href="#bcddresult-">⬆️ &nbsp;back to top</a>)</p>
240
+ *PS: The `divide()` implementation is [here](#result-hooks).*
241
+
242
+ <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
224
243
 
225
244
  #### `result.on_failure`
226
245
 
@@ -240,9 +259,11 @@ divide(4, 0).on_success { |number| puts number }
240
259
  divide(4, 0).on_failure(:invalid_arg) { |error| puts error }
241
260
  ```
242
261
 
243
- <p align="right">(<a href="#bcddresult-">⬆️ &nbsp;back to top</a>)</p>
262
+ *PS: The `divide()` implementation is [here](#result-hooks).*
263
+
264
+ <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
244
265
 
245
- ### Result value
266
+ ### Result Value
246
267
 
247
268
  The most simple way to get the result value is by calling `BCDD::Result#value` or `BCDD::Result#data`.
248
269
 
@@ -271,17 +292,16 @@ divide(4, '2').value_or { 0 } # 0
271
292
  divide(100, 0).value_or { 0 } # 0
272
293
  ```
273
294
 
274
- <p align="right">(<a href="#bcddresult-">⬆️ &nbsp;back to top</a>)</p>
295
+ *PS: The `divide()` implementation is [here](#result-hooks).*
296
+
297
+ <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
275
298
 
276
299
  #### `result.data_or`
277
300
 
278
301
  `BCDD::Result#data_or` is an alias of `BCDD::Result#value_or`.
279
302
 
280
-
281
303
  ### Railway Oriented Programming
282
304
 
283
- #### `result.and_then`
284
-
285
305
  This feature/pattern is also known as ["Railway Oriented Programming"](https://fsharpforfunandprofit.com/rop/).
286
306
 
287
307
  The idea is to chain blocks and creates a pipeline of operations that can be interrupted by a failure.
@@ -289,7 +309,9 @@ The idea is to chain blocks and creates a pipeline of operations that can be int
289
309
  In other words, the block will be executed only if the result is a success.
290
310
  So, if some block returns a failure, the following blocks will be skipped.
291
311
 
292
- Due to this characteristic, you can use this feature to express some logic as a sequence of operations. And have the guarantee that the process will stop by the first failure detection, and if everything is ok, the final result will be a success. e.g.,
312
+ Due to this characteristic, you can use this feature to express some logic as a sequence of operations. And have the guarantee that the process will stop by the first failure detection, and if everything is ok, the final result will be a success.
313
+
314
+ #### `result.and_then`
293
315
 
294
316
  ```ruby
295
317
  module Divide
@@ -313,7 +335,7 @@ module Divide
313
335
  def validate_non_zero(numbers)
314
336
  return BCDD::Result::Success(:ok, numbers) unless numbers.last.zero?
315
337
 
316
- BCDD::Result::Failure(:division_by_zero, "arg2 must not be zero")
338
+ BCDD::Result::Failure(:division_by_zero, 'arg2 must not be zero')
317
339
  end
318
340
 
319
341
  def divide((number1, number2))
@@ -338,13 +360,119 @@ Divide.call(2, 2)
338
360
  #<BCDD::Result::Success type=:division_completed data=1>
339
361
  ```
340
362
 
341
- <p align="right">(<a href="#bcddresult-">⬆️ &nbsp;back to top</a>)</p>
363
+ <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
364
+
365
+ #### `BCDD::Resultable`
366
+
367
+ It is a module that can be included/extended by any object. It adds two methods to the target object: `Success()` and `Failure()`.
368
+
369
+ The main difference between these methods and `BCDD::Result::Success()`/`BCDD::Result::Failure()` is that the first ones will use the target object as the result's subject.
370
+
371
+ And because of this, you can use the `#and_then` method to call methods from the target object (result's subject).
372
+
373
+ ##### Class example (instance methods)
374
+
375
+ ```ruby
376
+ class Divide
377
+ include BCDD::Resultable
378
+
379
+ attr_reader :arg1, :arg2
380
+
381
+ def initialize(arg1, arg2)
382
+ @arg1 = arg1
383
+ @arg2 = arg2
384
+ end
385
+
386
+ def call
387
+ validate_numbers
388
+ .and_then(:validate_non_zero)
389
+ .and_then(:divide)
390
+ end
391
+
392
+ private
393
+
394
+ def validate_numbers
395
+ arg1.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
396
+ arg2.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
397
+
398
+ # As arg1 and arg2 are instance methods, they will be available in the instance scope.
399
+ # So, in this case, I'm passing them as an array to show how the next method can receive the value as its argument.
400
+ Success(:ok, [arg1, arg2])
401
+ end
402
+
403
+ def validate_non_zero(numbers)
404
+ return Success(:ok, numbers) unless numbers.last.zero?
405
+
406
+ Failure(:division_by_zero, 'arg2 must not be zero')
407
+ end
408
+
409
+ def divide((number1, number2))
410
+ Success(:division_completed, number1 / number2)
411
+ end
412
+ end
413
+
414
+ Divide.new(4, 2).call #<BCDD::Result::Success type=:division_completed value=2>
415
+
416
+ Divide.new(4, 0).call #<BCDD::Result::Failure type=:division_by_zero value="arg2 must not be zero">
417
+ Divide.new('4', 2).call #<BCDD::Result::Failure type=:invalid_arg value="arg1 must be numeric">
418
+ Divide.new(4, '2').call #<BCDD::Result::Failure type=:invalid_arg value="arg2 must be numeric">
419
+ ```
420
+
421
+ ##### Module example (singleton methods)
422
+
423
+ ```ruby
424
+ module Divide
425
+ extend BCDD::Resultable
426
+ extend self
427
+
428
+ def call(arg1, arg2)
429
+ validate_numbers(arg1, arg2)
430
+ .and_then(:validate_non_zero)
431
+ .and_then(:divide)
432
+ end
433
+
434
+ private
435
+
436
+ def validate_numbers(arg1, arg2)
437
+ arg1.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
438
+ arg2.is_a?(::Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
439
+
440
+ Success(:ok, [arg1, arg2])
441
+ end
442
+
443
+ def validate_non_zero(numbers)
444
+ return Success(:ok, numbers) unless numbers.last.zero?
445
+
446
+ Failure(:division_by_zero, 'arg2 must not be zero')
447
+ end
448
+
449
+ def divide((number1, number2))
450
+ Success(:division_completed, number1 / number2)
451
+ end
452
+ end
453
+
454
+ Divide.call(4, 2) #<BCDD::Result::Success type=:division_completed value=2>
455
+
456
+ Divide.call(4, 0) #<BCDD::Result::Failure type=:division_by_zero value="arg2 must not be zero">
457
+ Divide.call('4', 2) #<BCDD::Result::Failure type=:invalid_arg value="arg1 must be numeric">
458
+ Divide.call(4, '2') #<BCDD::Result::Failure type=:invalid_arg value="arg2 must be numeric">
459
+ ```
460
+
461
+ ##### Restrictions
462
+
463
+ 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
+
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.
466
+
467
+ > **Note**: You still can use the block syntax, but all the results must be produced by the subject's `Success()` and `Failure()` methods.
468
+
469
+ <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
342
470
 
343
471
  ## About
344
472
 
345
473
  [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.
346
474
 
347
- <p align="right">(<a href="#bcddresult-">⬆️ &nbsp;back to top</a>)</p>
475
+ <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
348
476
 
349
477
  ## Development
350
478
 
@@ -352,19 +480,19 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
352
480
 
353
481
  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).
354
482
 
355
- <p align="right">(<a href="#bcddresult-">⬆️ &nbsp;back to top</a>)</p>
483
+ <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
356
484
 
357
485
  ## Contributing
358
486
 
359
487
  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).
360
488
 
361
- <p align="right">(<a href="#bcddresult-">⬆️ &nbsp;back to top</a>)</p>
489
+ <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
362
490
 
363
491
  ## License
364
492
 
365
493
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
366
494
 
367
- <p align="right">(<a href="#bcddresult-">⬆️ &nbsp;back to top</a>)</p>
495
+ <p align="right">(<a href="#-bcddresult">⬆️ &nbsp;back to top</a>)</p>
368
496
 
369
497
  ## Code of Conduct
370
498
 
@@ -2,6 +2,10 @@
2
2
 
3
3
  class BCDD::Result
4
4
  class Error < ::StandardError
5
+ def self.build(**_kargs)
6
+ new
7
+ end
8
+
5
9
  class NotImplemented < self
6
10
  end
7
11
 
@@ -11,13 +15,31 @@ class BCDD::Result
11
15
  end
12
16
  end
13
17
 
14
- class UnexpectedBlockOutcome < self
15
- def initialize(arg = nil)
18
+ class UnexpectedOutcome < self
19
+ def self.build(outcome:, origin:)
16
20
  message =
17
- "Unexpected outcome: #{arg.inspect}. The block must return this object wrapped by " \
21
+ "Unexpected outcome: #{outcome.inspect}. The #{origin} must return this object wrapped by " \
18
22
  'BCDD::Result::Success or BCDD::Result::Failure'
19
23
 
20
- super(message)
24
+ new(message)
25
+ end
26
+ end
27
+
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}"
35
+
36
+ new(message)
37
+ end
38
+ end
39
+
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.")
21
43
  end
22
44
  end
23
45
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module BCDD
4
4
  class Result
5
- VERSION = '0.1.0'
5
+ VERSION = '0.2.0'
6
6
  end
7
7
  end
data/lib/bcdd/result.rb CHANGED
@@ -5,12 +5,17 @@ require_relative 'result/error'
5
5
  require_relative 'result/failure'
6
6
  require_relative 'result/success'
7
7
 
8
+ require_relative 'resultable'
9
+
8
10
  class BCDD::Result
9
- attr_reader :type, :value
11
+ attr_reader :type, :value, :subject
12
+
13
+ protected :subject
10
14
 
11
- def initialize(type:, value:)
15
+ def initialize(type:, value:, subject: nil)
12
16
  @type = type.to_sym
13
17
  @value = value
18
+ @subject = subject
14
19
  end
15
20
 
16
21
  def success?(_type = nil)
@@ -52,14 +57,14 @@ class BCDD::Result
52
57
  tap { yield(value, type) if failure? && allowed_to_handle?(types) }
53
58
  end
54
59
 
55
- def and_then
60
+ def and_then(method_name = nil)
56
61
  return self if failure?
57
62
 
58
- result = yield(value)
63
+ return call_subject_method(method_name) if method_name
59
64
 
60
- return result if result.is_a?(::BCDD::Result)
65
+ result = yield(value)
61
66
 
62
- raise Error::UnexpectedBlockOutcome, result
67
+ ensure_result_object(result, origin: :block)
63
68
  end
64
69
 
65
70
  alias data value
@@ -75,4 +80,25 @@ class BCDD::Result
75
80
  def allowed_to_handle?(types)
76
81
  types.empty? || expected_type?(types)
77
82
  end
83
+
84
+ def call_subject_method(method_name)
85
+ method = subject.method(method_name)
86
+
87
+ result =
88
+ case method.arity
89
+ when 0 then subject.send(method_name)
90
+ when 1 then subject.send(method_name, value)
91
+ else raise Error::WrongSubjectMethodArity.build(subject: subject, method: method)
92
+ end
93
+
94
+ ensure_result_object(result, origin: :method)
95
+ end
96
+
97
+ def ensure_result_object(result, origin:)
98
+ raise Error::UnexpectedOutcome.build(outcome: result, origin: origin) unless result.is_a?(::BCDD::Result)
99
+
100
+ return result if result.subject.equal?(subject)
101
+
102
+ raise Error::WrongResultSubject.build(given_result: result, expected_subject: subject)
103
+ end
78
104
  end
@@ -0,0 +1,11 @@
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
data/sig/bcdd/result.rbs CHANGED
@@ -7,8 +7,9 @@ end
7
7
  class BCDD::Result
8
8
  attr_reader type: Symbol
9
9
  attr_reader value: untyped
10
+ attr_reader subject: untyped
10
11
 
11
- def initialize: (type: Symbol, value: untyped) -> void
12
+ def initialize: (type: Symbol, value: untyped, ?subject: untyped) -> void
12
13
 
13
14
  def success?: (?Symbol type) -> bool
14
15
  def failure?: (?Symbol type) -> bool
@@ -26,7 +27,7 @@ class BCDD::Result
26
27
  def on_success: (*Symbol) { (untyped, Symbol) -> void } -> BCDD::Result
27
28
  def on_failure: (*Symbol) { (untyped, Symbol) -> void } -> BCDD::Result
28
29
 
29
- def and_then: { (untyped) -> untyped } -> BCDD::Result
30
+ def and_then: (?Symbol method_name) { (untyped) -> untyped } -> BCDD::Result
30
31
 
31
32
  alias data value
32
33
  alias data_or value_or
@@ -36,31 +37,55 @@ class BCDD::Result
36
37
 
37
38
  def expected_type?: (Array[Symbol]) -> bool
38
39
  def allowed_to_handle?: (Array[Symbol]) -> bool
40
+ def call_subject_method: (Symbol) -> BCDD::Result
41
+ def ensure_result_object: (untyped, origin: Symbol) -> BCDD::Result
39
42
  end
40
43
 
41
44
  class BCDD::Result
42
45
  class Failure < BCDD::Result
43
46
  end
44
47
 
45
- def self.Success: (Symbol type, untyped value) -> BCDD::Result::Success
48
+ def self.Success: (Symbol type, ?untyped value) -> BCDD::Result::Success
46
49
  end
47
50
 
48
51
  class BCDD::Result
49
52
  class Success < BCDD::Result
50
53
  end
51
54
 
52
- def self.Failure: (Symbol type, untyped value) -> BCDD::Result::Failure
55
+ def self.Failure: (Symbol type, ?untyped value) -> BCDD::Result::Failure
56
+ end
57
+
58
+ module BCDD
59
+ module Resultable
60
+ def Success: (Symbol type, ?untyped value) -> BCDD::Result::Success
61
+
62
+ def Failure: (Symbol type, ?untyped value) -> BCDD::Result::Failure
63
+ end
53
64
  end
54
65
 
55
66
  class BCDD::Result
56
67
  class Error < ::StandardError
68
+ def self.build: (**untyped) -> BCDD::Result::Error
69
+
57
70
  class NotImplemented < BCDD::Result::Error
58
71
  end
59
72
 
60
73
  class MissingTypeArgument < BCDD::Result::Error
61
74
  end
62
75
 
63
- class UnexpectedBlockOutcome < BCDD::Result::Error
76
+ class UnexpectedOutcome < BCDD::Result::Error
77
+ def self.build: (outcome: untyped, origin: Symbol)
78
+ -> BCDD::Result::Error::UnexpectedOutcome
79
+ end
80
+
81
+ class WrongResultSubject < BCDD::Result::Error
82
+ def self.build: (given_result: BCDD::Result, expected_subject: untyped)
83
+ -> BCDD::Result::Error::WrongResultSubject
84
+ end
85
+
86
+ class WrongSubjectMethodArity < BCDD::Result::Error
87
+ def self.build: (subject: untyped, method: ::Method)
88
+ -> BCDD::Result::Error::WrongSubjectMethodArity
64
89
  end
65
90
  end
66
91
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bcdd-result
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.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-25 00:00:00.000000000 Z
11
+ date: 2023-09-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A general-purpose result monad that allows you to create objects that
14
14
  represent a success (BCDD::Result::Success) or failure (BCDD::Result::Failure).
@@ -31,6 +31,7 @@ files:
31
31
  - lib/bcdd/result/failure.rb
32
32
  - lib/bcdd/result/success.rb
33
33
  - lib/bcdd/result/version.rb
34
+ - lib/bcdd/resultable.rb
34
35
  - sig/bcdd/result.rbs
35
36
  homepage: https://github.com/b-cdd/result
36
37
  licenses: