resonad 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.rspec +4 -0
- data/.ruby-version +1 -0
- data/.travis.yml +17 -5
- data/CHANGELOG.md +50 -0
- data/README.md +247 -21
- data/lib/resonad.rb +78 -16
- data/lib/resonad/version.rb +1 -1
- data/resonad.gemspec +2 -3
- metadata +11 -24
- data/Rakefile +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2751182e01f6cf2c97cdc99d46cb716ec1e517d52680bc75c92fd0f5c1bd693f
|
4
|
+
data.tar.gz: 2d1a5a21e81f7da804fa099d16a8b3a2032472e6f89112e4284942d050087293
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6571a2f2e4e2ae98dee8c40efbf0bccf0aed09b6bc18cce47a5fb3fc83cc77c32141b31807d9b64b2842c62dfc737025ac24234dc22a1285f0e9910dae97cced
|
7
|
+
data.tar.gz: a000acabecf552daebeff7f10b1e57ab44f7fba224e8e85deb25acb7904b0c97981453931f008b68547b2fdade0e3ebec8d8b868af6757f4271a4a434f075cdb
|
data/.rspec
CHANGED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.7
|
data/.travis.yml
CHANGED
@@ -1,13 +1,25 @@
|
|
1
1
|
language: ruby
|
2
|
+
script: bundle exec rspec
|
3
|
+
|
4
|
+
# test old rubies
|
2
5
|
rvm:
|
3
|
-
- 2.2.
|
4
|
-
- 2.3.
|
5
|
-
- 2.4.
|
6
|
-
|
6
|
+
- 2.2.10
|
7
|
+
- 2.3.8
|
8
|
+
- 2.4.10
|
9
|
+
- 2.5.8
|
10
|
+
- 2.6.6
|
11
|
+
|
12
|
+
# test on latest ruby
|
13
|
+
matrix:
|
14
|
+
include:
|
15
|
+
- rvm: 2.7.1
|
16
|
+
env: LATEST_RUBY=true
|
17
|
+
|
18
|
+
# Rubygems deployment
|
7
19
|
deploy:
|
8
20
|
provider: rubygems
|
9
21
|
on:
|
10
22
|
tags: true
|
11
|
-
|
23
|
+
condition: $LATEST_RUBY = true
|
12
24
|
api_key:
|
13
25
|
secure: 0+R2SaUaKOZE0U4FwHiC/0DLepizJ1anjfvFEWk5pYVkaZl6HuaSYq50g8ff4VqXBH955Y5W7tFI8gfg4ZY9jrivHyEZT9Yoz3PFFPxcqSdVbDsV12RQt7ZjzW0HbvUhFu9nlrVACfFsLt4WFG61pyhXvewghp1p4JBp2UxeTk1kyL7U+wfWsp1RpcKiHkaLBeWJ0N87j4QkhTfcWlModLqN/19ATigvfyDjrwerkTescVmQi4TzfAiwkeAiDgUB32FkwJjbX9RfIko33CsuctuDRU/HT+RWiXcGAxdHZx4dtQpP9pAiNTVxXdIq+QAvitekIah70pdCTNKrOh3tDj3kglkUK23MqU6kdeXjmUn9r4SuzHCX0sVf16UpfnwuryDDPlG1ISY+mf7BARr/wNQWuAD4MA4kWiPGMqT/0uoysbY7dt44lkO9NV7HBbqs2shqqMtmgPgDoJ+SGTXo8LvrjuL0jHB2/MoHziiqWDKLQ9PS2Fcqp/06d5u8GcSRt7dcgo2rvwnMwRdUW6iCV0zQgzrNccWn/SsJROtgzEIkuLJYO0GIOCgNBJ0euorWqQBEEPniltwh5StSgH2bL5khdPxiI+LhsL7UDhWGiNFl5tlQp8Ob98WmmQ39ZyVAGKlJoe2rdE/IaDVfysVvQV5XzYUVRn4ZKftwubo5Zvw=
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Changelog
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## [1.3.0] - 2020-07-12
|
8
|
+
### Added
|
9
|
+
- more documentation
|
10
|
+
- support for Ruby 2.7 pattern matching
|
11
|
+
- `#otherwise` alias for `#or_else`
|
12
|
+
- `#map_value` alias for `#map`
|
13
|
+
- alternate constructor methods
|
14
|
+
- aliases for `#on_success` and `#on_failure`
|
15
|
+
- `Success` and `Failure` class constants to `Resonad::Mixin`
|
16
|
+
- `Resonad::PublicMixin` for people who want the previous behaviour of
|
17
|
+
`Resonad::Mixin`
|
18
|
+
### Changed
|
19
|
+
- The methods provided by `Resonad::Mixin` are now private. Replace with
|
20
|
+
`Resonad::PublicMixin` to revert them back to being public.
|
21
|
+
|
22
|
+
## [1.2.0] - 2017-05-22
|
23
|
+
### Added
|
24
|
+
- `Resonad.Success()` and `Resonad.Failure()` arguments are optional, and
|
25
|
+
default to nil.
|
26
|
+
- `#successful?` and `#ok?` as new aliases for `#success?`
|
27
|
+
- `#failed?` and `#bad?` as new aliases for `#failure?`
|
28
|
+
|
29
|
+
## [1.1.1] - 2017-05-09
|
30
|
+
### Fixed
|
31
|
+
- Aliased methods `#and_then` and `#or_else` were not aliased properly.
|
32
|
+
|
33
|
+
## [1.1.0] - 2017-05-01
|
34
|
+
### Added
|
35
|
+
- `Resonad::Mixin`
|
36
|
+
### Changed
|
37
|
+
- `Resonad` is now a class. `Resonad::Success` and `Resonad::Failure` now
|
38
|
+
inherit from `Resonad`.
|
39
|
+
|
40
|
+
## [1.0.2] - 2017-04-22
|
41
|
+
### Fixed
|
42
|
+
- Typo in class names
|
43
|
+
|
44
|
+
## [1.0.1] - 2017-04-22
|
45
|
+
### Changed
|
46
|
+
- Nothing (bumped to force a deploy)
|
47
|
+
|
48
|
+
## [1.0.0] - 2017-04-22
|
49
|
+
### Added
|
50
|
+
- Everything (initial release)
|
data/README.md
CHANGED
@@ -1,15 +1,26 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/tomdalling/resonad.svg?branch=master)](https://travis-ci.org/tomdalling/resonad)
|
2
|
+
|
1
3
|
# Resonad
|
2
4
|
|
3
|
-
|
5
|
+
Lightweight, functional "result" objects that can be used instead of exceptions.
|
6
|
+
|
7
|
+
Read: [Result Objects - Errors Without Exceptions](https://www.rubypigeon.com/posts/result-objects-errors-without-exceptions/)
|
8
|
+
|
9
|
+
## Typical Usage Example
|
10
|
+
|
11
|
+
Assuming each method returns a `Resonad` (Ruby 2.7 syntax):
|
4
12
|
|
5
13
|
```ruby
|
6
14
|
find_widget(widget_id)
|
7
|
-
.and_then {
|
8
|
-
.on_success {
|
9
|
-
.on_failure {
|
15
|
+
.and_then { update_widget(_1) }
|
16
|
+
.on_success { logger.info("Updated #{_1}" }
|
17
|
+
.on_failure { logger.warn("Widget update failed because #{_1}") }
|
10
18
|
```
|
11
19
|
|
12
|
-
Success
|
20
|
+
## Success Type
|
21
|
+
|
22
|
+
A value that represents success. Wraps a `value` that can be any arbitrary
|
23
|
+
object.
|
13
24
|
|
14
25
|
```ruby
|
15
26
|
result = Resonad.Success(5)
|
@@ -19,7 +30,10 @@ result.value #=> 5
|
|
19
30
|
result.error #=> raises an exception
|
20
31
|
```
|
21
32
|
|
22
|
-
Failure
|
33
|
+
## Failure Type
|
34
|
+
|
35
|
+
A value that represents a failure. Wraps an `error` that can be any arbitrary
|
36
|
+
object.
|
23
37
|
|
24
38
|
```ruby
|
25
39
|
result = Resonad.Failure(:buzz)
|
@@ -29,39 +43,199 @@ result.value #=> raises an exception
|
|
29
43
|
result.error #=> :buzz
|
30
44
|
```
|
31
45
|
|
32
|
-
Mapping
|
46
|
+
## Mapping
|
47
|
+
|
48
|
+
Non-destructive update for the `value` of a `Success` object. Does nothing to
|
49
|
+
`Failure` objects.
|
50
|
+
|
51
|
+
The block takes the `value` as an argument, and returns the new `value`.
|
33
52
|
|
34
53
|
```ruby
|
35
54
|
result = Resonad.Success(5)
|
36
|
-
.map {
|
37
|
-
.map {
|
38
|
-
.map {
|
55
|
+
.map { _1 + 1 } # 5 + 1 -> 6
|
56
|
+
.map { _1 + 1 } # 6 + 1 -> 7
|
57
|
+
.map { _1 + 1 } # 7 + 1 -> 8
|
39
58
|
result.success? #=> true
|
40
59
|
result.value #=> 8
|
41
60
|
|
42
61
|
result = Resonad.Failure(:buzz)
|
43
|
-
.map {
|
44
|
-
.map {
|
45
|
-
.map {
|
62
|
+
.map { _1 + 1 } # not run
|
63
|
+
.map { _1 + 1 } # not run
|
64
|
+
.map { _1 + 1 } # not run
|
46
65
|
result.success? #=> false
|
47
66
|
result.error #=> :buzz
|
48
67
|
```
|
49
68
|
|
50
|
-
|
69
|
+
|
70
|
+
## Aliases
|
71
|
+
|
72
|
+
Lots of the Resonad methods have aliases.
|
73
|
+
|
74
|
+
Personally, I can never remember if it's `success?` or `successful?` or `ok?`,
|
75
|
+
so let's just do it the Ruby way and allow all of them.
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
# >>> object creation aliases (same for failure) <<<
|
79
|
+
result = Resonad.Success(5)
|
80
|
+
result = Resonad.success(5) # lowercase, for those offended by capital letters
|
81
|
+
result = Resonad::Success[5] # class constructor method
|
82
|
+
|
83
|
+
# >>> success aliases <<<
|
84
|
+
result.success? #=> true
|
85
|
+
result.successful? #=> true
|
86
|
+
result.ok? #=> true
|
87
|
+
|
88
|
+
# >>> failure aliases <<<
|
89
|
+
result.failure? #=> false
|
90
|
+
result.failed? #=> false
|
91
|
+
result.bad? #=> false
|
92
|
+
|
93
|
+
# >>> mapping aliases <<<
|
94
|
+
result.map { _1 + 1 } #=> Success(6)
|
95
|
+
result.map_value { _1 + 1 } #=> Success(6)
|
96
|
+
|
97
|
+
# >>> flat mapping aliases <<<
|
98
|
+
result.and_then { Resonad.Success(_1 + 1) } #=> Success(6)
|
99
|
+
result.flat_map { Resonad.Success(_1 + 1) } #=> Success(6)
|
100
|
+
|
101
|
+
# >>> error flat mapping aliases <<<
|
102
|
+
result.or_else { Resonad.Failure(_1 + 1) } # not run
|
103
|
+
result.otherwise { Resonad.Failure(_1 + 1) } # not run
|
104
|
+
result.flat_map_error { Resonad.Success(_1 + 1) } # not run
|
105
|
+
|
106
|
+
# >>> conditional tap aliases <<<
|
107
|
+
# pattern: (on_|if_|when_)(success_alias|failure_alias)
|
108
|
+
result.on_success { puts "hi" } # outputs "hi"
|
109
|
+
result.if_success { puts "hi" } # outputs "hi"
|
110
|
+
result.when_success { puts "hi" } # outputs "hi"
|
111
|
+
result.on_ok { puts "hi" } # outputs "hi"
|
112
|
+
result.if_ok { puts "hi" } # outputs "hi"
|
113
|
+
result.when_ok { puts "hi" } # outputs "hi"
|
114
|
+
result.on_successful { puts "hi" } # outputs "hi"
|
115
|
+
result.if_successful { puts "hi" } # outputs "hi"
|
116
|
+
result.when_successful { puts "hi" } # outputs "hi"
|
117
|
+
result.on_failure { puts "hi" } # not run
|
118
|
+
result.if_failure { puts "hi" } # not run
|
119
|
+
result.when_failure { puts "hi" } # not run
|
120
|
+
result.on_bad { puts "hi" } # not run
|
121
|
+
result.if_bad { puts "hi" } # not run
|
122
|
+
result.when_bad { puts "hi" } # not run
|
123
|
+
result.on_failed { puts "hi" } # not run
|
124
|
+
result.if_failed { puts "hi" } # not run
|
125
|
+
result.when_failed { puts "hi" } # not run
|
126
|
+
```
|
127
|
+
|
128
|
+
|
129
|
+
## Flat Mapping (a.k.a. `and_then`)
|
130
|
+
|
131
|
+
Non-destructive update for a `Success` object. Either turns it into another
|
132
|
+
`Success` (can have a different `value`), or turns it into a `Failure`. Does
|
133
|
+
nothing to `Failure` objects.
|
134
|
+
|
135
|
+
The block takes the `value` as an argument, and returns a `Resonad` (either
|
136
|
+
`Success` or `Failure`).
|
51
137
|
|
52
138
|
```ruby
|
53
139
|
result = Resonad.Success(5)
|
54
|
-
.and_then {
|
55
|
-
.and_then {
|
56
|
-
.and_then {
|
140
|
+
.and_then { Resonad.Success(_1 + 1) } # updates to Success(6)
|
141
|
+
.and_then { Resonad.Failure("buzz #{_1}") } # updates to Failure("buzz 6")
|
142
|
+
.and_then { Resonad.Success(_1 + 1) } # not run (because it's a failure)
|
57
143
|
.error #=> "buzz 6"
|
58
144
|
|
59
|
-
#
|
60
|
-
result
|
61
|
-
.flat_map { |i| Resonad.Success(i + 1) }
|
145
|
+
# also has a less-friendly but more-technically-descriptive alias: `flat_map`
|
146
|
+
result.flat_map { Resonad.Success(_1 + 1) }
|
62
147
|
```
|
63
148
|
|
64
|
-
|
149
|
+
This is different to Ruby's `#then` method added in 2.6. The block for `#then`
|
150
|
+
would take a Resonad argument, regardless of whether it's `Success` or
|
151
|
+
`Failure`. The block for `#and_then` takes a _`Success` object's value_, and
|
152
|
+
only runs on `Success` objects, not `Failure` objects.
|
153
|
+
|
154
|
+
|
155
|
+
## Error Mapping
|
156
|
+
|
157
|
+
Just as `Success` objects can be chained with `#map` and `#and_then`, so can
|
158
|
+
`Failure` objects with `#map_error` and `#or_else`. This isn't used as often,
|
159
|
+
but has a few use cases such as:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
# Use Case: convert an error value into another error value
|
163
|
+
make_http_request #=> Failure(404)
|
164
|
+
.map_error { |status_code| "HTTP #{status_code} Error" }
|
165
|
+
.error #=> "HTTP 404 Error"
|
166
|
+
|
167
|
+
# Use Case: recover from error, turning into Success
|
168
|
+
load_config_file #=> Failure(:config_file_missing)
|
169
|
+
.or_else { try_recover_from(_1) }
|
170
|
+
.value #=> { :setting => 'default' }
|
171
|
+
|
172
|
+
def try_recover_from(error)
|
173
|
+
if error == :config_file_missing
|
174
|
+
Resonad.Success({ setting: 'default' })
|
175
|
+
else
|
176
|
+
Resonad.Failure(error)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
```
|
180
|
+
|
181
|
+
|
182
|
+
## Conditional Tap
|
183
|
+
|
184
|
+
If you're in the middle of a long chain of methods, and you don't want to break
|
185
|
+
the chain to run some kind of side effect, you can use the `#on_success` and
|
186
|
+
`#on_failure` methods. These run an arbitrary block code, but do not affect the
|
187
|
+
result object in any way. They work like Ruby's `#tap` method, but `Failure`
|
188
|
+
objects will not run `on_success` blocks, and `Success` objects will not run
|
189
|
+
`on_failure` blocks.
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
do_step_1
|
193
|
+
.and_then { do_step_2(_1) }
|
194
|
+
.and_then { do_step_3(_1) }
|
195
|
+
.on_success { puts "Successful step 3 result: #{_1}" }
|
196
|
+
.and_then { do_step_4(_1) }
|
197
|
+
.and_then { do_step_5(_1) }
|
198
|
+
.on_failure { puts "Uh oh! Step 5 failed: #{_1} }
|
199
|
+
.and_then { do_step_6(_1) }
|
200
|
+
.and_then { do_step_7(_1) }
|
201
|
+
```
|
202
|
+
|
203
|
+
There are lots of aliases for these methods. See the "Aliases" section above.
|
204
|
+
|
205
|
+
|
206
|
+
## Pattern Matching Support
|
207
|
+
|
208
|
+
If you are using Ruby 2.7 or later, you can pattern match on Resonad objects.
|
209
|
+
For example:
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
case result
|
213
|
+
in { value: } # match any Success
|
214
|
+
puts value
|
215
|
+
in { error: :not_found } # match Failure(:not_found)
|
216
|
+
puts "Thing not found"
|
217
|
+
in { error: String => msg } # match any Failure with a String error
|
218
|
+
puts "Failed to fetch thing because #{msg}"
|
219
|
+
in { error: } # match any Failure
|
220
|
+
raise "Unhandled error: #{error.inspect}"
|
221
|
+
end
|
222
|
+
```
|
223
|
+
|
224
|
+
`Resonad.Success(5)` deconstructs to:
|
225
|
+
|
226
|
+
- Hash: `{ value: 5 }`
|
227
|
+
- Array: `[:success, 5]`
|
228
|
+
|
229
|
+
And `Resonad.Failure('yikes')` deconstructs to:
|
230
|
+
|
231
|
+
- Hash: `{ error: 'yikes' }`
|
232
|
+
- Array: `[:failure, 'yikes']`
|
233
|
+
|
234
|
+
|
235
|
+
## Automatic Exception Rescuing
|
236
|
+
|
237
|
+
If no exception is raised, wraps the block's return value in `Success`. If an
|
238
|
+
exception is raised, wraps the exception object in `Failure`.
|
65
239
|
|
66
240
|
```ruby
|
67
241
|
def try_divide(top, bottom)
|
@@ -76,3 +250,55 @@ nope = try_divide(6, 0)
|
|
76
250
|
nope.success? #=> false
|
77
251
|
node.error #=> #<ZeroDivisionError: ZeroDivisionError>
|
78
252
|
```
|
253
|
+
|
254
|
+
|
255
|
+
## Convenience Mixin
|
256
|
+
|
257
|
+
If you're tired of typing "Resonad." in front of everything, you can include
|
258
|
+
the `Resonad::Mixin` mixin.
|
259
|
+
|
260
|
+
```ruby
|
261
|
+
class RobotFortuneTeller
|
262
|
+
include Resonad::Mixin
|
263
|
+
|
264
|
+
def next_fortune
|
265
|
+
case rand(0..100)
|
266
|
+
when 0..70
|
267
|
+
# title-case constructor from Resonad::Mixin
|
268
|
+
Success("today is auspicious")
|
269
|
+
when 71..95
|
270
|
+
# lower-case constructor from Resonad::Mixin
|
271
|
+
success("ill omens abound")
|
272
|
+
else
|
273
|
+
# direct access to classes from Resonad::Mixin
|
274
|
+
Failure.new("MALFUNCTION")
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
```
|
279
|
+
|
280
|
+
Note that `Resonad::Mixin` provides private methods, and private constants, so
|
281
|
+
you can't do this:
|
282
|
+
|
283
|
+
```ruby
|
284
|
+
RobotFortuneTeller.new.Success(5)
|
285
|
+
#=> NoMethodError: private method `Success' called for #<RobotFortuneTeller:0x00007fe7fc0ff0c8>
|
286
|
+
|
287
|
+
RobotFortuneTeller::Success
|
288
|
+
#=> NameError: private constant Resonad::Mixin::Success referenced
|
289
|
+
```
|
290
|
+
|
291
|
+
If you want the methods/constants to be public, then use `Resonad::PublicMixin`
|
292
|
+
instead.
|
293
|
+
|
294
|
+
|
295
|
+
## Contributing
|
296
|
+
|
297
|
+
Bug reports and pull requests are welcome on GitHub at:
|
298
|
+
https://github.com/tomdalling/resonad
|
299
|
+
|
300
|
+
I'm open to PRs that make the gem more convenient, or that makes calling code
|
301
|
+
read better.
|
302
|
+
|
303
|
+
Make sure your PR has full test coverage.
|
304
|
+
|
data/lib/resonad.rb
CHANGED
@@ -2,28 +2,17 @@ class Resonad
|
|
2
2
|
class NonExistentError < StandardError; end
|
3
3
|
class NonExistentValue < StandardError; end
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
class Success < Resonad
|
6
|
+
attr_accessor :value
|
7
|
+
|
8
|
+
def self.[](value = nil)
|
7
9
|
if nil == value
|
8
10
|
NIL_SUCCESS
|
9
11
|
else
|
10
|
-
|
12
|
+
new(value)
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
14
|
-
def Failure(error=nil)
|
15
|
-
if nil == error
|
16
|
-
NIL_FAILURE
|
17
|
-
else
|
18
|
-
Failure.new(error)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
extend Mixin
|
23
|
-
|
24
|
-
class Success < Resonad
|
25
|
-
attr_accessor :value
|
26
|
-
|
27
16
|
def initialize(value)
|
28
17
|
@value = value
|
29
18
|
freeze
|
@@ -66,11 +55,27 @@ class Resonad
|
|
66
55
|
def flat_map_error
|
67
56
|
self
|
68
57
|
end
|
58
|
+
|
59
|
+
def deconstruct
|
60
|
+
[:success, value]
|
61
|
+
end
|
62
|
+
|
63
|
+
def deconstruct_keys(_)
|
64
|
+
{ value: value }
|
65
|
+
end
|
69
66
|
end
|
70
67
|
|
71
68
|
class Failure < Resonad
|
72
69
|
attr_accessor :error
|
73
70
|
|
71
|
+
def self.[](error = nil)
|
72
|
+
if nil == error
|
73
|
+
NIL_FAILURE
|
74
|
+
else
|
75
|
+
new(error)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
74
79
|
def initialize(error)
|
75
80
|
@error = error
|
76
81
|
freeze
|
@@ -113,8 +118,35 @@ class Resonad
|
|
113
118
|
def flat_map_error
|
114
119
|
yield error
|
115
120
|
end
|
121
|
+
|
122
|
+
def deconstruct
|
123
|
+
[:failure, error]
|
124
|
+
end
|
125
|
+
|
126
|
+
def deconstruct_keys(_)
|
127
|
+
{ error: error }
|
128
|
+
end
|
116
129
|
end
|
117
130
|
|
131
|
+
module PublicMixin
|
132
|
+
Success = ::Resonad::Success
|
133
|
+
Failure = ::Resonad::Failure
|
134
|
+
|
135
|
+
def Success(*args); Success[*args]; end
|
136
|
+
def success(*args); Success[*args]; end
|
137
|
+
def Failure(*args); Failure[*args]; end
|
138
|
+
def failure(*args); Failure[*args]; end
|
139
|
+
end
|
140
|
+
|
141
|
+
Mixin = PublicMixin.dup.tap do |mixin|
|
142
|
+
mixin.module_eval do
|
143
|
+
private(*public_instance_methods)
|
144
|
+
private_constant(*constants)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
extend PublicMixin
|
149
|
+
|
118
150
|
def self.rescuing_from(*exception_classes)
|
119
151
|
Success(yield)
|
120
152
|
rescue Exception => e
|
@@ -143,6 +175,11 @@ class Resonad
|
|
143
175
|
def failed?; failure?; end
|
144
176
|
def bad?; failure?; end
|
145
177
|
|
178
|
+
def map(&block)
|
179
|
+
raise NotImplementedError, "should be implemented in subclass"
|
180
|
+
end
|
181
|
+
def map_value(&block); map(&block); end
|
182
|
+
|
146
183
|
def flat_map
|
147
184
|
raise NotImplementedError, "should be implemented in subclass"
|
148
185
|
end
|
@@ -152,6 +189,31 @@ class Resonad
|
|
152
189
|
raise NotImplementedError, "should be implemented in subclass"
|
153
190
|
end
|
154
191
|
def or_else(&block); flat_map_error(&block); end
|
192
|
+
def otherwise(&block); flat_map_error(&block); end
|
193
|
+
|
194
|
+
def on_success(&block)
|
195
|
+
raise NotImplementedError, "should be implemented in subclass"
|
196
|
+
end
|
197
|
+
def if_success(&block); on_success(&block); end
|
198
|
+
def when_success(&block); on_success(&block); end
|
199
|
+
def on_ok(&block); on_success(&block); end
|
200
|
+
def if_ok(&block); on_success(&block); end
|
201
|
+
def when_ok(&block); on_success(&block); end
|
202
|
+
def on_successful(&block); on_success(&block); end
|
203
|
+
def if_successful(&block); on_success(&block); end
|
204
|
+
def when_successful(&block); on_success(&block); end
|
205
|
+
|
206
|
+
def on_failure(&block)
|
207
|
+
raise NotImplementedError, "should be implemented in subclass"
|
208
|
+
end
|
209
|
+
def if_failure(&block); on_failure(&block); end
|
210
|
+
def when_failure(&block); on_failure(&block); end
|
211
|
+
def on_bad(&block); on_failure(&block); end
|
212
|
+
def if_bad(&block); on_failure(&block); end
|
213
|
+
def when_bad(&block); on_failure(&block); end
|
214
|
+
def on_failed(&block); on_failure(&block); end
|
215
|
+
def if_failed(&block); on_failure(&block); end
|
216
|
+
def when_failed(&block); on_failure(&block); end
|
155
217
|
|
156
218
|
NIL_SUCCESS = Success.new(nil)
|
157
219
|
NIL_FAILURE = Failure.new(nil)
|
data/lib/resonad/version.rb
CHANGED
data/resonad.gemspec
CHANGED
@@ -20,8 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
21
|
spec.require_paths = ["lib"]
|
22
22
|
|
23
|
-
spec.add_development_dependency "bundler", "
|
24
|
-
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "bundler", ">= 1.15"
|
25
24
|
spec.add_development_dependency "rspec", "~> 3.0"
|
26
|
-
spec.add_development_dependency "gem-release", "~>
|
25
|
+
spec.add_development_dependency "gem-release", "~> 2.1"
|
27
26
|
end
|
metadata
CHANGED
@@ -1,43 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: resonad
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom Dalling
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-07-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1.
|
19
|
+
version: '1.15'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '1.14'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: rake
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '10.0'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
24
|
+
- - ">="
|
39
25
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
26
|
+
version: '1.15'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: rspec
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,14 +44,14 @@ dependencies:
|
|
58
44
|
requirements:
|
59
45
|
- - "~>"
|
60
46
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
47
|
+
version: '2.1'
|
62
48
|
type: :development
|
63
49
|
prerelease: false
|
64
50
|
version_requirements: !ruby/object:Gem::Requirement
|
65
51
|
requirements:
|
66
52
|
- - "~>"
|
67
53
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
54
|
+
version: '2.1'
|
69
55
|
description: Objects that represent success or failure
|
70
56
|
email:
|
71
57
|
- tom@tomdalling.com
|
@@ -75,12 +61,13 @@ extra_rdoc_files: []
|
|
75
61
|
files:
|
76
62
|
- ".gitignore"
|
77
63
|
- ".rspec"
|
64
|
+
- ".ruby-version"
|
78
65
|
- ".travis.yml"
|
66
|
+
- CHANGELOG.md
|
79
67
|
- CODE_OF_CONDUCT.md
|
80
68
|
- Gemfile
|
81
69
|
- LICENSE.txt
|
82
70
|
- README.md
|
83
|
-
- Rakefile
|
84
71
|
- bin/console
|
85
72
|
- bin/setup
|
86
73
|
- lib/resonad.rb
|
@@ -106,7 +93,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
93
|
version: '0'
|
107
94
|
requirements: []
|
108
95
|
rubyforge_project:
|
109
|
-
rubygems_version: 2.
|
96
|
+
rubygems_version: 2.7.7
|
110
97
|
signing_key:
|
111
98
|
specification_version: 4
|
112
99
|
summary: Objects that represent success or failure
|