resonad 1.2.0 → 1.3.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 +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
|
+
[](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
|