dry-monads 1.3.5 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +161 -80
- data/LICENSE +1 -1
- data/README.md +5 -4
- data/dry-monads.gemspec +30 -30
- data/lib/dry/monads/all.rb +2 -3
- data/lib/dry/monads/constants.rb +0 -2
- data/lib/dry/monads/curry.rb +2 -2
- data/lib/dry/monads/do/all.rb +35 -18
- data/lib/dry/monads/do.rb +48 -20
- data/lib/dry/monads/errors.rb +8 -5
- data/lib/dry/monads/lazy.rb +13 -5
- data/lib/dry/monads/list.rb +27 -37
- data/lib/dry/monads/maybe.rb +85 -26
- data/lib/dry/monads/registry.rb +20 -20
- data/lib/dry/monads/result/fixed.rb +31 -24
- data/lib/dry/monads/result.rb +37 -19
- data/lib/dry/monads/right_biased.rb +38 -31
- data/lib/dry/monads/task.rb +25 -28
- data/lib/dry/monads/transformer.rb +2 -1
- data/lib/dry/monads/traverse.rb +5 -1
- data/lib/dry/monads/try.rb +45 -18
- data/lib/dry/monads/unit.rb +9 -3
- data/lib/dry/monads/validated.rb +18 -18
- data/lib/dry/monads/version.rb +1 -1
- data/lib/dry/monads.rb +25 -4
- data/lib/dry-monads.rb +1 -1
- data/lib/json/add/dry/monads/maybe.rb +5 -5
- metadata +22 -68
- data/.codeclimate.yml +0 -12
- data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +0 -10
- data/.github/ISSUE_TEMPLATE/---bug-report.md +0 -30
- data/.github/ISSUE_TEMPLATE/---feature-request.md +0 -18
- data/.github/workflows/ci.yml +0 -52
- data/.github/workflows/docsite.yml +0 -34
- data/.github/workflows/sync_configs.yml +0 -56
- data/.gitignore +0 -10
- data/.rspec +0 -4
- data/.rubocop.yml +0 -101
- data/.yardopts +0 -4
- data/CODE_OF_CONDUCT.md +0 -13
- data/CONTRIBUTING.md +0 -29
- data/Gemfile +0 -19
- data/Gemfile.devtools +0 -14
- data/Rakefile +0 -8
- data/bin/.gitkeep +0 -0
- data/bin/console +0 -17
- data/bin/setup +0 -7
- data/docsite/source/case-equality.html.md +0 -42
- data/docsite/source/do-notation.html.md +0 -207
- data/docsite/source/getting-started.html.md +0 -142
- data/docsite/source/index.html.md +0 -179
- data/docsite/source/list.html.md +0 -87
- data/docsite/source/maybe.html.md +0 -146
- data/docsite/source/pattern-matching.html.md +0 -68
- data/docsite/source/result.html.md +0 -190
- data/docsite/source/task.html.md +0 -126
- data/docsite/source/tracing-failures.html.md +0 -32
- data/docsite/source/try.html.md +0 -76
- data/docsite/source/unit.html.md +0 -36
- data/docsite/source/validated.html.md +0 -88
- data/lib/dry/monads/either.rb +0 -66
- data/log/.gitkeep +0 -0
- data/project.yml +0 -2
@@ -1,190 +0,0 @@
|
|
1
|
-
---
|
2
|
-
title: Result
|
3
|
-
layout: gem-single
|
4
|
-
name: dry-monads
|
5
|
-
---
|
6
|
-
|
7
|
-
The `Result` monad is useful to express a series of computations that might
|
8
|
-
return an error object with additional information.
|
9
|
-
|
10
|
-
The `Result` mixin has two type constructors: `Success` and `Failure`. The `Success`
|
11
|
-
can be thought of as "everything went success" and the `Failure` is used when
|
12
|
-
"something has gone wrong".
|
13
|
-
|
14
|
-
### `bind`
|
15
|
-
|
16
|
-
Use `bind` for composing several possibly-failing operations:
|
17
|
-
|
18
|
-
```ruby
|
19
|
-
require 'dry/monads'
|
20
|
-
|
21
|
-
class AssociateUser
|
22
|
-
include Dry::Monads[:result]
|
23
|
-
|
24
|
-
def call(user_id:, address_id:)
|
25
|
-
find_user(user_id).bind do |user|
|
26
|
-
find_address(address_id).fmap do |address|
|
27
|
-
user.update(address_id: address.id)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def find_user(id)
|
35
|
-
user = User.find_by(id: id)
|
36
|
-
|
37
|
-
if user
|
38
|
-
Success(user)
|
39
|
-
else
|
40
|
-
Failure(:user_not_found)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def find_address(id)
|
45
|
-
address = Address.find_by(id: id)
|
46
|
-
|
47
|
-
if address
|
48
|
-
Success(address)
|
49
|
-
else
|
50
|
-
Failure(:address_not_found)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
AssociateUser.new.(user_id: 1, address_id: 2)
|
56
|
-
```
|
57
|
-
|
58
|
-
### `fmap`
|
59
|
-
|
60
|
-
An example of using `fmap` with `Success` and `Failure`.
|
61
|
-
|
62
|
-
```ruby
|
63
|
-
extend Dry::Monads[:result]
|
64
|
-
|
65
|
-
result = if foo > bar
|
66
|
-
Success(10)
|
67
|
-
else
|
68
|
-
Failure("wrong")
|
69
|
-
end.fmap { |x| x * 2 }
|
70
|
-
|
71
|
-
# If everything went success
|
72
|
-
result # => Success(20)
|
73
|
-
# If it did not
|
74
|
-
result # => Failure("wrong")
|
75
|
-
|
76
|
-
# #fmap accepts a proc, just like #bind
|
77
|
-
|
78
|
-
upcase = :upcase.to_proc
|
79
|
-
|
80
|
-
Success('hello').fmap(upcase) # => Success("HELLO")
|
81
|
-
```
|
82
|
-
|
83
|
-
### `value_or`
|
84
|
-
|
85
|
-
`value_or` is a safe and recommended way of extracting values.
|
86
|
-
|
87
|
-
```ruby
|
88
|
-
extend Dry::Monads[:result]
|
89
|
-
|
90
|
-
Success(10).value_or(0) # => 10
|
91
|
-
Failure('Error').value_or(0) # => 0
|
92
|
-
```
|
93
|
-
|
94
|
-
### `value!`
|
95
|
-
|
96
|
-
If you're 100% sure you're dealing with a `Success` case you might use `value!` for extracting the value without providing a default. Beware, this will raise an exception if you call it on `Failure`.
|
97
|
-
|
98
|
-
```ruby
|
99
|
-
extend Dry::Monads[:result]
|
100
|
-
|
101
|
-
Success(10).value! # => 10
|
102
|
-
Failure('Error').value!
|
103
|
-
# => Dry::Monads::UnwrapError: value! was called on Failure
|
104
|
-
```
|
105
|
-
|
106
|
-
### `or`
|
107
|
-
|
108
|
-
An example of using `or` with `Success` and `Failure`.
|
109
|
-
|
110
|
-
```ruby
|
111
|
-
extend Dry::Monads[:result]
|
112
|
-
|
113
|
-
Success(10).or(Success(99)) # => Success(10)
|
114
|
-
Failure("error").or(Failure("new error")) # => Failure("new error")
|
115
|
-
Failure("error").or { |err| Failure("new #{err}") } # => Failure("new error")
|
116
|
-
```
|
117
|
-
|
118
|
-
### `failure`
|
119
|
-
|
120
|
-
Use `failure` for unwrapping the value from a `Failure` instance.
|
121
|
-
|
122
|
-
```ruby
|
123
|
-
extend Dry::Monads[:result]
|
124
|
-
|
125
|
-
Failure('Error').failure # => "Error"
|
126
|
-
```
|
127
|
-
|
128
|
-
### `to_maybe`
|
129
|
-
|
130
|
-
Sometimes it's useful to turn a `Result` into a `Maybe`.
|
131
|
-
|
132
|
-
```ruby
|
133
|
-
extend Dry::Monads[:result, :maybe]
|
134
|
-
|
135
|
-
result = if foo > bar
|
136
|
-
Success(10)
|
137
|
-
else
|
138
|
-
Failure("wrong")
|
139
|
-
end.to_maybe
|
140
|
-
|
141
|
-
# If everything went success
|
142
|
-
result # => Some(10)
|
143
|
-
# If it did not
|
144
|
-
result # => None()
|
145
|
-
```
|
146
|
-
|
147
|
-
### `failure?` and `success?`
|
148
|
-
|
149
|
-
You can explicitly check the type by calling `failure?` or `success?` on a monadic value.
|
150
|
-
|
151
|
-
### `either`
|
152
|
-
|
153
|
-
`either` maps a `Result` to some type by taking two callables, for `Success` and `Failure` cases respectively:
|
154
|
-
|
155
|
-
```ruby
|
156
|
-
Success(1).either(-> x { x + 1 }, -> x { x + 2 }) # => 2
|
157
|
-
Failure(1).either(-> x { x + 1 }, -> x { x + 2 }) # => 3
|
158
|
-
```
|
159
|
-
|
160
|
-
|
161
|
-
### Adding constraints to `Failure` values.
|
162
|
-
You can add type constraints to values passed to `Failure`. This will raise an exception if value doesn't meet the constraints:
|
163
|
-
|
164
|
-
```ruby
|
165
|
-
require 'dry-types'
|
166
|
-
|
167
|
-
module Types
|
168
|
-
include Dry.Types()
|
169
|
-
end
|
170
|
-
|
171
|
-
class Operation
|
172
|
-
Error = Types.Instance(RangeError)
|
173
|
-
include Dry::Monads::Result(Error)
|
174
|
-
|
175
|
-
def call(value)
|
176
|
-
case value
|
177
|
-
when 0..1
|
178
|
-
Success(:success)
|
179
|
-
when -Float::INFINITY..0, 1..Float::INFINITY
|
180
|
-
Failure(RangeError.new('Error'))
|
181
|
-
else
|
182
|
-
Failure(TypeError.new('Type error'))
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
Operation.new.call(0.5) # => Success(:success)
|
188
|
-
Operation.new.call(5) # => Failure(#<RangeError: Error>)
|
189
|
-
Operation.new.call("5") # => Dry::Monads::InvalidFailureTypeError: Cannot create Failure from #<TypeError: Type error>, it doesn't meet the constraints
|
190
|
-
```
|
data/docsite/source/task.html.md
DELETED
@@ -1,126 +0,0 @@
|
|
1
|
-
---
|
2
|
-
title: Task
|
3
|
-
layout: gem-single
|
4
|
-
name: dry-monads
|
5
|
-
---
|
6
|
-
|
7
|
-
`Task` represents an asynchronous computation. It is similar to the `IO` type in a sense it can be used to wrap side-effectful actions. `Task`s are usually run on a thread pool but also can be executed immediately on the current thread. Internally, `Task` uses `Promise` from the [`concurrent-ruby`](https://github.com/ruby-concurrency/concurrent-ruby) gem, basically it's a thin wrapper with a monadic interface which makes it easily composable with other monads.
|
8
|
-
|
9
|
-
### `Task::Mixin`
|
10
|
-
|
11
|
-
Basic usage.
|
12
|
-
|
13
|
-
```ruby
|
14
|
-
require 'dry/monads'
|
15
|
-
|
16
|
-
class PullUsersWithPosts
|
17
|
-
include Dry::Monads[:task]
|
18
|
-
|
19
|
-
def call
|
20
|
-
# Start two tasks running concurrently
|
21
|
-
users = Task { fetch_users }
|
22
|
-
posts = Task { fetch_posts }
|
23
|
-
|
24
|
-
# Combine two tasks
|
25
|
-
users.bind { |us| posts.fmap { |ps| [us, ps] } }
|
26
|
-
end
|
27
|
-
|
28
|
-
def fetch_users
|
29
|
-
sleep 3
|
30
|
-
[{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]
|
31
|
-
end
|
32
|
-
|
33
|
-
def fetch_posts
|
34
|
-
sleep 2
|
35
|
-
[
|
36
|
-
{ id: 1, user_id: 1, name: 'Hello from John' },
|
37
|
-
{ id: 2, user_id: 2, name: 'Hello from Jane' },
|
38
|
-
]
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
# PullUsersWithPosts instance
|
43
|
-
pull = PullUsersWithPosts.new
|
44
|
-
|
45
|
-
# Spin up two tasks
|
46
|
-
task = pull.call
|
47
|
-
|
48
|
-
task.fmap do |users, posts|
|
49
|
-
puts "Users: #{ users.inspect }"
|
50
|
-
puts "Posts: #{ posts.inspect }"
|
51
|
-
end
|
52
|
-
|
53
|
-
puts "----" # this will be printed before the lines above
|
54
|
-
```
|
55
|
-
|
56
|
-
### Executors
|
57
|
-
|
58
|
-
Tasks are performed by executors, there are three executors predefined by `concurrent-ruby` identified by symbols:
|
59
|
-
|
60
|
-
- `:fast` – for fast asynchronous tasks, uses a thread pool
|
61
|
-
- `:io` – for long IO-bound tasks, uses a thread pool, different from `:fast`
|
62
|
-
- `:immediate` – runs tasks immediately, on the current thread. Can be used in tests or for other purposes
|
63
|
-
|
64
|
-
You can create your own executors, check out the [docs](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent.html) for more on this.
|
65
|
-
|
66
|
-
The following examples use the Ruby 2.5+ syntax which allows passing a block to `.[]`.
|
67
|
-
|
68
|
-
```ruby
|
69
|
-
Task[:io] { do_http_request }
|
70
|
-
|
71
|
-
Task[:fast] { cpu_intensive_computation }
|
72
|
-
|
73
|
-
Task[:immediate] { unsafe_io_operation }
|
74
|
-
|
75
|
-
# You can pass an executor object
|
76
|
-
Task[my_executor] { ... }
|
77
|
-
```
|
78
|
-
|
79
|
-
### Exception handling
|
80
|
-
|
81
|
-
All exceptions happening in `Task` are captured, even if you're using the `:immediate` executor, they won't be re-raised.
|
82
|
-
|
83
|
-
```ruby
|
84
|
-
io_fail = Task[:io] { 1/0 }
|
85
|
-
io_fail # => Task(error=#<ZeroDivisionError: divided by 0>)
|
86
|
-
|
87
|
-
immediate_fail = Task[:immediate] { 1/0 }
|
88
|
-
immediate_fail # => Task(error=#<ZeroDivisionError: divided by 0>)
|
89
|
-
```
|
90
|
-
|
91
|
-
You can process failures with `or` and `or_fmap`:
|
92
|
-
|
93
|
-
```ruby
|
94
|
-
Task[:immediate] { 1/0 }.or { M::Task[:immediate] { 0 } } # => Task(value=0)
|
95
|
-
Task[:immediate] { 1/0 }.or_fmap { 0 } # => Task(value=0)
|
96
|
-
```
|
97
|
-
|
98
|
-
### Extracting result
|
99
|
-
|
100
|
-
Getting the result of a task is an unsafe operation, it blocks the current thread until the task is finished, then returns the value or raises an exception if the evaluation wasn't sucessful. It effectively cancels all niceties of tasks so you shouldn't use it in production code.
|
101
|
-
|
102
|
-
```ruby
|
103
|
-
Task { 0 }.value! # => 0
|
104
|
-
Task { 1/0 }.value! # => ZeroDivisionError: divided by 0
|
105
|
-
```
|
106
|
-
|
107
|
-
You can wait for a task to complete, the `wait` method accepts an optional timeout. `.wait` returns the task back, without unwrapping the result so it's a blocking yet safe operation:
|
108
|
-
|
109
|
-
```ruby
|
110
|
-
Task[:io] { 2 }.wait(1) # => Task(value=2)
|
111
|
-
Task[:io] { sleep 2; 2 }.wait(1) # => Task(?)
|
112
|
-
|
113
|
-
# (?) denotes an unfinished computation
|
114
|
-
```
|
115
|
-
|
116
|
-
### Conversions
|
117
|
-
|
118
|
-
Tasks can be converted to other monads but keep in mind that all conversions block the current thread:
|
119
|
-
|
120
|
-
```ruby
|
121
|
-
Task[:io] { 2 }.to_result # => Success(2)
|
122
|
-
Task[:io] { 1/0 }.to_result # => Failure(#<ZeroDivisionError: divided by 0>)
|
123
|
-
|
124
|
-
Task[:io] { 2 }.to_maybe # => Some(2)
|
125
|
-
Task[:io] { 1/0 }.to_maybe # => None
|
126
|
-
```
|
@@ -1,32 +0,0 @@
|
|
1
|
-
---
|
2
|
-
title: Tracing failures
|
3
|
-
layout: gem-single
|
4
|
-
name: dry-monads
|
5
|
-
---
|
6
|
-
|
7
|
-
"Left" values of right-biased monads like `Maybe` and `Result` (as in, `None()` and `Failure()`) ignore blocks passed to `fmap` and `bind`. Because of this, these values travel across the application without any modification. If the place where a `Failure` was constructed is burried somewhere deep in the app or library code it may be pretty hard to find out where exactly the error occurred.
|
8
|
-
|
9
|
-
This is a noticable downside compared to "good" old exceptions. To address it, every `Failure(...)` and `None()` value tracks the line where it was created:
|
10
|
-
|
11
|
-
```ruby
|
12
|
-
# create_user.rb
|
13
|
-
require 'dry/monads'
|
14
|
-
|
15
|
-
class CreateUser
|
16
|
-
include Dry::Monads[:result]
|
17
|
-
|
18
|
-
def call
|
19
|
-
Failure(:no_luck)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
```
|
23
|
-
|
24
|
-
```ruby
|
25
|
-
require 'create_user'
|
26
|
-
|
27
|
-
create_user = CreateUser.new
|
28
|
-
create_user.() # => Failure(:no_luck)
|
29
|
-
create_user.().trace # => .../create_user.rb:8:in `call'
|
30
|
-
```
|
31
|
-
|
32
|
-
Note that the trace stores only one line of the stack so it shouldn't ever be a performance issue.
|
data/docsite/source/try.html.md
DELETED
@@ -1,76 +0,0 @@
|
|
1
|
-
---
|
2
|
-
title: Try
|
3
|
-
layout: gem-single
|
4
|
-
name: dry-monads
|
5
|
-
---
|
6
|
-
|
7
|
-
Rescues a block from an exception. The `Try` monad is useful when you want to wrap some code that can raise exceptions of certain types. A common example is making an HTTP request or querying a database.
|
8
|
-
|
9
|
-
```ruby
|
10
|
-
require 'dry/monads'
|
11
|
-
|
12
|
-
class ExceptionalLand
|
13
|
-
include Dry::Monads[:try]
|
14
|
-
|
15
|
-
|
16
|
-
def call
|
17
|
-
res = Try { 10 / 2 }
|
18
|
-
res.value! if res.value?
|
19
|
-
# => 5
|
20
|
-
|
21
|
-
res = Try { 10 / 0 }
|
22
|
-
res.exception if res.error?
|
23
|
-
# => #<ZeroDivisionError: divided by 0>
|
24
|
-
|
25
|
-
# By default Try catches all exceptions inherited from StandardError.
|
26
|
-
# However you can catch only certain exceptions like this
|
27
|
-
Try[NoMethodError, NotImplementedError] { 10 / 0 }
|
28
|
-
# => raised ZeroDivisionError: divided by 0 exception
|
29
|
-
end
|
30
|
-
end
|
31
|
-
```
|
32
|
-
|
33
|
-
It is better if you pass a list of expected exceptions which you are sure you can process. Catching exceptions of all types is considered bad practice.
|
34
|
-
|
35
|
-
The `Try` monad consists of two types: `Value` and `Error`. The first is returned when code did not raise an error and the second is returned when the error was captured.
|
36
|
-
|
37
|
-
### `bind`
|
38
|
-
|
39
|
-
Allows you to chain blocks that can raise exceptions.
|
40
|
-
|
41
|
-
```ruby
|
42
|
-
Try[NetworkError, DBError] { grap_user_by_making_request }.bind { |user| user_repo.save(user) }
|
43
|
-
|
44
|
-
# Possible outcomes:
|
45
|
-
# => Value(persisted_user)
|
46
|
-
# => Error(NetworkError: request timeout)
|
47
|
-
# => Error(DBError: unique constraint violated)
|
48
|
-
```
|
49
|
-
|
50
|
-
### `fmap`
|
51
|
-
|
52
|
-
Works exactly the same way as `Result#fmap` does.
|
53
|
-
|
54
|
-
```ruby
|
55
|
-
require 'dry/monads'
|
56
|
-
|
57
|
-
class ExceptionalLand
|
58
|
-
include Dry::Monads[:try]
|
59
|
-
|
60
|
-
def call
|
61
|
-
Try { 10 / 2 }.fmap { |x| x * 3 }
|
62
|
-
# => 15
|
63
|
-
|
64
|
-
Try[ZeroDivisionError] { 10 / 0 }.fmap { |x| x * 3 }
|
65
|
-
# => Failure(ZeroDivisionError: divided by 0)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
```
|
69
|
-
|
70
|
-
### `value!` and `exception`
|
71
|
-
|
72
|
-
Use `value!` for unwrapping a `Success` and `exception` for getting error object from a `Failure`.
|
73
|
-
|
74
|
-
### `to_result` and `to_maybe`
|
75
|
-
|
76
|
-
`Try`'s `Value` and `Error` can be transformed to `Success` and `Failure` correspondingly by calling `to_result` and to `Some` and `None` by calling `to_maybe`. Keep in mind that by transforming `Try` to `Maybe` you lose the information about an exception so be sure that you've processed the error before doing so.
|
data/docsite/source/unit.html.md
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
---
|
2
|
-
title: Unit
|
3
|
-
layout: gem-single
|
4
|
-
name: dry-monads
|
5
|
-
---
|
6
|
-
|
7
|
-
Some constructors do not require you to pass a value. As a default they use `Unit`, a special singleton value:
|
8
|
-
|
9
|
-
```ruby
|
10
|
-
extend Dry::Monads[:result]
|
11
|
-
|
12
|
-
Success().value! # => Unit
|
13
|
-
```
|
14
|
-
|
15
|
-
`Unit` doesn't have any special properties or methods, it's similar to `nil` except for it is not i.e. `if Unit` passes.
|
16
|
-
|
17
|
-
`Unit` is usually excluded from the output:
|
18
|
-
|
19
|
-
```ruby
|
20
|
-
extend Dry::Monads[:result]
|
21
|
-
|
22
|
-
# Outputs as "Success()" but technically it's "Success(Unit)"
|
23
|
-
Success()
|
24
|
-
```
|
25
|
-
|
26
|
-
### Discarding values
|
27
|
-
|
28
|
-
When the outcome of an operation is not a caller's concern, call `.discard`, it will map the wrapped value to `Unit`:
|
29
|
-
|
30
|
-
```ruby
|
31
|
-
extend Dry::Monads[:result]
|
32
|
-
|
33
|
-
result = create_user # returns Success(#<User...>) or Failure(...)
|
34
|
-
|
35
|
-
result.discard # => Maps Success(#<User ...>) to Success() but lefts Failure(...) intact
|
36
|
-
```
|
@@ -1,88 +0,0 @@
|
|
1
|
-
---
|
2
|
-
title: Validated
|
3
|
-
layout: gem-single
|
4
|
-
name: dry-monads
|
5
|
-
---
|
6
|
-
|
7
|
-
Suppose you've got a form to validate. If you are using `Result` combined with `Do` your code might look like this:
|
8
|
-
|
9
|
-
```ruby
|
10
|
-
require 'dry/monads'
|
11
|
-
|
12
|
-
class CreateAccount
|
13
|
-
include Dry::Monads[:result, :do]
|
14
|
-
|
15
|
-
def call(form)
|
16
|
-
name = yield validate_name(form)
|
17
|
-
email = yield validate_email(form)
|
18
|
-
password = yield validate_password(form)
|
19
|
-
|
20
|
-
user = repo.create_user(
|
21
|
-
name: name,
|
22
|
-
email: email,
|
23
|
-
password: password
|
24
|
-
)
|
25
|
-
|
26
|
-
Success(user)
|
27
|
-
end
|
28
|
-
|
29
|
-
def validate_name(form)
|
30
|
-
# Success(name) or Failure(:invalid_name)
|
31
|
-
end
|
32
|
-
|
33
|
-
def validate_email(form)
|
34
|
-
# Success(email) or Failure(:invalid_email)
|
35
|
-
end
|
36
|
-
|
37
|
-
def validate_password(form)
|
38
|
-
# Success(password) or Failure(:invalid_password)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
```
|
42
|
-
|
43
|
-
If any of the validation steps fails the user will see an error. The problem is if `name` is not valid the user won't see errors about invalid `email` and `password`, if any. `Validated` circumvents this particular problem.
|
44
|
-
|
45
|
-
`Validated` is actually not a monad but an applicative functor. This means you can't call `bind` on it. Instead, it can accumulate values in combination with `List`:
|
46
|
-
|
47
|
-
```ruby
|
48
|
-
require 'dry/monads'
|
49
|
-
|
50
|
-
class CreateAccount
|
51
|
-
include Dry::Monads[:list, :result, :validated, :do]
|
52
|
-
|
53
|
-
def call(form)
|
54
|
-
name, email, password = yield List::Validated[
|
55
|
-
validate_name(form),
|
56
|
-
validate_email(form),
|
57
|
-
validate_password(form)
|
58
|
-
].traverse.to_result
|
59
|
-
|
60
|
-
user = repo.create_user(
|
61
|
-
name: name,
|
62
|
-
email: email,
|
63
|
-
password: password
|
64
|
-
)
|
65
|
-
|
66
|
-
Success(user)
|
67
|
-
end
|
68
|
-
|
69
|
-
def validate_name(form)
|
70
|
-
# Valid(name) or Invalid(:invalid_name)
|
71
|
-
end
|
72
|
-
|
73
|
-
def validate_email(form)
|
74
|
-
# Valid(email) or Invalid(:invalid_email)
|
75
|
-
end
|
76
|
-
|
77
|
-
def validate_password(form)
|
78
|
-
# Valid(password) or Invalid(:invalid_password)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
```
|
82
|
-
|
83
|
-
Here all validations will be processed at once, if any of them fails the result will be converted to a `Failure` wrapping the `List` of errors:
|
84
|
-
|
85
|
-
```ruby
|
86
|
-
create_account.(form)
|
87
|
-
# => Failure(List[:invalid_name, :invalid_email])
|
88
|
-
```
|
data/lib/dry/monads/either.rb
DELETED
@@ -1,66 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'dry/core/deprecations'
|
4
|
-
|
5
|
-
Dry::Core::Deprecations.warn('Either monad was renamed to Result', tag: :'dry-monads')
|
6
|
-
|
7
|
-
require 'dry/monads/result'
|
8
|
-
|
9
|
-
module Dry
|
10
|
-
module Monads
|
11
|
-
Either = Result
|
12
|
-
deprecate_constant :Either
|
13
|
-
|
14
|
-
class Result
|
15
|
-
extend Dry::Core::Deprecations[:'dry-monads']
|
16
|
-
|
17
|
-
deprecate :to_either, :to_result
|
18
|
-
|
19
|
-
Right = Success
|
20
|
-
Left = Failure
|
21
|
-
|
22
|
-
deprecate_constant :Right
|
23
|
-
deprecate_constant :Left
|
24
|
-
|
25
|
-
module Mixin
|
26
|
-
module Constructors
|
27
|
-
extend Dry::Core::Deprecations[:'dry-monads']
|
28
|
-
|
29
|
-
Right = Success
|
30
|
-
Left = Failure
|
31
|
-
deprecate_constant :Right
|
32
|
-
deprecate_constant :Left
|
33
|
-
|
34
|
-
deprecate :Right, :Success
|
35
|
-
deprecate :Left, :Failure
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
class Success
|
40
|
-
deprecate :left?, :failure?
|
41
|
-
deprecate :right?, :success?
|
42
|
-
end
|
43
|
-
|
44
|
-
class Failure
|
45
|
-
deprecate :left?, :failure?
|
46
|
-
deprecate :right?, :success?
|
47
|
-
|
48
|
-
deprecate :left, :failure
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
class Try
|
53
|
-
class Value
|
54
|
-
extend Dry::Core::Deprecations[:'dry-monads']
|
55
|
-
|
56
|
-
deprecate :to_either, :to_result
|
57
|
-
end
|
58
|
-
|
59
|
-
class Error
|
60
|
-
extend Dry::Core::Deprecations[:'dry-monads']
|
61
|
-
|
62
|
-
deprecate :to_either, :to_result
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
data/log/.gitkeep
DELETED
File without changes
|
data/project.yml
DELETED