bcdd-result 0.1.0 → 0.2.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: 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: