u-case 2.6.0 → 3.0.0.rc5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.sh +3 -2
- data/Gemfile +11 -2
- data/README.md +472 -441
- data/README.pt-BR.md +1390 -0
- data/lib/micro/case.rb +96 -77
- data/lib/micro/case/config.rb +23 -0
- data/lib/micro/case/error.rb +24 -18
- data/lib/micro/case/result.rb +94 -55
- data/lib/micro/case/safe.rb +8 -4
- data/lib/micro/case/utils.rb +7 -0
- data/lib/micro/case/version.rb +1 -1
- data/lib/micro/case/with_activemodel_validation.rb +4 -2
- data/lib/micro/cases.rb +16 -0
- data/lib/micro/cases/flow.rb +86 -0
- data/lib/micro/cases/safe/flow.rb +18 -0
- data/lib/u-case/with_activemodel_validation.rb +0 -2
- data/u-case.gemspec +3 -3
- metadata +19 -12
- data/lib/micro/case/flow.rb +0 -48
- data/lib/micro/case/flow/reducer.rb +0 -104
- data/lib/micro/case/safe/flow.rb +0 -44
- data/lib/u-case/with_validation.rb +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 12b63a467f7516217f29a5705859141be80fc2fb2209c41e77d7c8a365745b8c
|
4
|
+
data.tar.gz: 4501314217a04cca7d56c0091a8a2c02f93f308f51c323462af36c71532be1b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 85351b75b4e2e2e62e66f2dff078a607ffa29e5c0df93dc3f6ce07dbfd0444ecd3034358fc08b358698c7ca2ccd73dbc5b8ab597ce339c5d7f4970df504b8a99
|
7
|
+
data.tar.gz: 5ef5552ca31fc9d9474f9a54717c7ce9a45ad9aba46710bf6671ae5836a492981beb4bee3cbdfbb5418770bcd896a634846eb659b5eaf1c098d6fa0c9339dc7b
|
data/.travis.sh
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
#!/bin/bash
|
2
2
|
|
3
|
-
bundle exec rake test
|
4
|
-
|
5
3
|
ruby_v=$(ruby -v)
|
6
4
|
|
7
5
|
ACTIVEMODEL_VERSION='3.2' bundle update
|
@@ -16,3 +14,6 @@ if [[ $ruby_v =~ '2.5.' ]] || [[ $ruby_v =~ '2.6.' ]] || [[ $ruby_v =~ '2.7.' ]]
|
|
16
14
|
ACTIVEMODEL_VERSION='6.0' bundle update
|
17
15
|
ACTIVEMODEL_VERSION='6.0' bundle exec rake test
|
18
16
|
fi
|
17
|
+
|
18
|
+
bundle update
|
19
|
+
bundle exec rake test
|
data/Gemfile
CHANGED
@@ -22,14 +22,23 @@ end
|
|
22
22
|
|
23
23
|
pry_byebug_version =
|
24
24
|
case RUBY_VERSION
|
25
|
-
when /\A2.
|
26
|
-
when /\A2.3/ then '3.7'
|
25
|
+
when /\A2.[23]/ then '3.6'
|
27
26
|
else '3.9'
|
28
27
|
end
|
29
28
|
|
29
|
+
pry_version =
|
30
|
+
case RUBY_VERSION
|
31
|
+
when /\A2.2/ then '0.12.2'
|
32
|
+
when /\A2.3/ then '0.12.2'
|
33
|
+
else '0.13.1'
|
34
|
+
end
|
35
|
+
|
30
36
|
group :development, :test do
|
31
37
|
gem 'awesome_print', '~> 1.8'
|
32
38
|
|
39
|
+
gem 'byebug', '~> 10.0', '>= 10.0.2' if RUBY_VERSION =~ /\A2.[23]/
|
40
|
+
|
41
|
+
gem 'pry', "~> #{pry_version}"
|
33
42
|
gem 'pry-byebug', "~> #{pry_byebug_version}"
|
34
43
|
end
|
35
44
|
|
data/README.md
CHANGED
@@ -4,22 +4,31 @@
|
|
4
4
|
[![Maintainability](https://api.codeclimate.com/v1/badges/5c3c8ad1b0b943f88efd/maintainability)](https://codeclimate.com/github/serradura/u-case/maintainability)
|
5
5
|
[![Test Coverage](https://api.codeclimate.com/v1/badges/5c3c8ad1b0b943f88efd/test_coverage)](https://codeclimate.com/github/serradura/u-case/test_coverage)
|
6
6
|
|
7
|
-
|
8
|
-
====================
|
7
|
+
<img src="./assets/ucase_logo_v1.png" alt="u-case - Create simple and powerful use cases as Ruby objects.">
|
9
8
|
|
10
|
-
Create simple and powerful use cases as objects.
|
9
|
+
Create simple and powerful use cases as Ruby objects.
|
11
10
|
|
12
11
|
The main project goals are:
|
13
12
|
1. Easy to use and easy to learn (input **>>** process **>>** output).
|
14
|
-
2. Promote
|
15
|
-
3. No callbacks (
|
16
|
-
4. Solve complex business logic, by allowing the composition of use cases.
|
13
|
+
2. Promote immutability (transforming data instead of modifying it) and data integrity.
|
14
|
+
3. No callbacks (ex: before, after, around) to avoid code indirections that could compromise the state and understanding of application flows.
|
15
|
+
4. Solve complex business logic, by allowing the composition of use cases (flow creation).
|
17
16
|
5. Be fast and optimized (Check out the [benchmarks](#benchmarks) section).
|
18
17
|
|
19
|
-
> Note
|
18
|
+
> **Note:** Check out the repo https://github.com/serradura/from-fat-controllers-to-use-cases to see a Rails application that uses this gem to handle its business logic.
|
19
|
+
|
20
|
+
## Documentation <!-- omit in toc -->
|
21
|
+
|
22
|
+
Version | Documentation
|
23
|
+
--------- | -------------
|
24
|
+
3.0.0.rc5 | https://github.com/serradura/u-case/blob/master/README.md
|
25
|
+
2.6.0 | https://github.com/serradura/u-case/blob/v2.x/README.md
|
26
|
+
1.1.0 | https://github.com/serradura/u-case/blob/v1.x/README.md
|
27
|
+
|
28
|
+
> **Note:** Você entende português? 🇧🇷🇵🇹 Verifique o [README traduzido em pt-BR](https://github.com/serradura/u-case/blob/main/README.pt-BR.md).
|
20
29
|
|
21
30
|
## Table of Contents <!-- omit in toc -->
|
22
|
-
- [
|
31
|
+
- [Compatibility](#compatibility)
|
23
32
|
- [Dependencies](#dependencies)
|
24
33
|
- [Installation](#installation)
|
25
34
|
- [Usage](#usage)
|
@@ -27,45 +36,54 @@ The main project goals are:
|
|
27
36
|
- [`Micro::Case::Result` - What is a use case result?](#microcaseresult---what-is-a-use-case-result)
|
28
37
|
- [What are the default result types?](#what-are-the-default-result-types)
|
29
38
|
- [How to define custom result types?](#how-to-define-custom-result-types)
|
30
|
-
- [Is it possible to define a custom
|
39
|
+
- [Is it possible to define a custom type without a result data?](#is-it-possible-to-define-a-custom-type-without-a-result-data)
|
31
40
|
- [How to use the result hooks?](#how-to-use-the-result-hooks)
|
32
|
-
- [Why the
|
41
|
+
- [Why the hook usage without a defined type exposes the result itself?](#why-the-hook-usage-without-a-defined-type-exposes-the-result-itself)
|
42
|
+
- [Using decomposition to access the result data and type](#using-decomposition-to-access-the-result-data-and-type)
|
33
43
|
- [What happens if a result hook was declared multiple times?](#what-happens-if-a-result-hook-was-declared-multiple-times)
|
34
44
|
- [How to use the `Micro::Case::Result#then` method?](#how-to-use-the-microcaseresultthen-method)
|
45
|
+
- [What does happens when a `Micro::Case::Result#then` receives a block?](#what-does-happens-when-a-microcaseresultthen-receives-a-block)
|
35
46
|
- [How to make attributes data injection using this feature?](#how-to-make-attributes-data-injection-using-this-feature)
|
36
|
-
- [`Micro::
|
37
|
-
- [Is it possible to compose a
|
47
|
+
- [`Micro::Cases::Flow` - How to compose use cases?](#microcasesflow---how-to-compose-use-cases)
|
48
|
+
- [Is it possible to compose a flow with other flows?](#is-it-possible-to-compose-a-flow-with-other-flows)
|
38
49
|
- [Is it possible a flow accumulates its input and merges each success result to use as the argument of the next use cases?](#is-it-possible-a-flow-accumulates-its-input-and-merges-each-success-result-to-use-as-the-argument-of-the-next-use-cases)
|
39
50
|
- [How to understand what is happening during a flow execution?](#how-to-understand-what-is-happening-during-a-flow-execution)
|
40
51
|
- [`Micro::Case::Result#transitions` schema](#microcaseresulttransitions-schema)
|
41
|
-
|
52
|
+
- [Is it possible disable the `Micro::Case::Result#transitions`?](#is-it-possible-disable-the-microcaseresulttransitions)
|
53
|
+
- [Is it possible to declare a flow that includes the use case itself as a step?](#is-it-possible-to-declare-a-flow-that-includes-the-use-case-itself-as-a-step)
|
42
54
|
- [`Micro::Case::Strict` - What is a strict use case?](#microcasestrict---what-is-a-strict-use-case)
|
43
55
|
- [`Micro::Case::Safe` - Is there some feature to auto handle exceptions inside of a use case or flow?](#microcasesafe---is-there-some-feature-to-auto-handle-exceptions-inside-of-a-use-case-or-flow)
|
44
|
-
- [`Micro::
|
56
|
+
- [`Micro::Cases::Safe::Flow`](#microcasessafeflow)
|
45
57
|
- [`Micro::Case::Result#on_exception`](#microcaseresulton_exception)
|
46
|
-
- [`u-case/with_activemodel_validation` - How to validate use case attributes?](#u-casewith_activemodel_validation---how-to-validate-use-case-attributes)
|
47
|
-
- [If I enabled the auto validation, is it possible to disable it only in specific use
|
58
|
+
- [`u-case/with_activemodel_validation` - How to validate the use case attributes?](#u-casewith_activemodel_validation---how-to-validate-the-use-case-attributes)
|
59
|
+
- [If I enabled the auto validation, is it possible to disable it only in specific use cases?](#if-i-enabled-the-auto-validation-is-it-possible-to-disable-it-only-in-specific-use-cases)
|
48
60
|
- [`Kind::Validator`](#kindvalidator)
|
61
|
+
- [`Micro::Case.config`](#microcaseconfig)
|
49
62
|
- [Benchmarks](#benchmarks)
|
50
|
-
- [`Micro::Case`](#microcase)
|
51
|
-
- [Best overall](#best-overall)
|
63
|
+
- [`Micro::Case` (v3.0.0)](#microcase-v300)
|
52
64
|
- [Success results](#success-results)
|
53
65
|
- [Failure results](#failure-results)
|
54
|
-
- [`Micro::
|
66
|
+
- [`Micro::Cases::Flow` (v3.0.0)](#microcasesflow-v300)
|
55
67
|
- [Comparisons](#comparisons)
|
56
68
|
- [Examples](#examples)
|
57
69
|
- [1️⃣ Rails App (API)](#1️⃣-rails-app-api)
|
58
70
|
- [2️⃣ CLI calculator](#2️⃣-cli-calculator)
|
59
71
|
- [3️⃣ Users creation](#3️⃣-users-creation)
|
60
|
-
- [4️⃣ Rescuing
|
72
|
+
- [4️⃣ Rescuing exceptions inside of the use cases](#4️⃣-rescuing-exceptions-inside-of-the-use-cases)
|
61
73
|
- [Development](#development)
|
62
74
|
- [Contributing](#contributing)
|
63
75
|
- [License](#license)
|
64
76
|
- [Code of Conduct](#code-of-conduct)
|
65
77
|
|
66
|
-
##
|
78
|
+
## Compatibility
|
67
79
|
|
68
|
-
|
80
|
+
| u-case | branch | ruby | activemodel |
|
81
|
+
| -------------- | ------- | -------- | ------------- |
|
82
|
+
| 3.0.0.rc5 | main | >= 2.2.0 | >= 3.2, < 6.1 |
|
83
|
+
| 2.6.0 | v2.x | >= 2.2.0 | >= 3.2, < 6.1 |
|
84
|
+
| 1.1.0 | v1.x | >= 2.2.0 | >= 3.2, < 6.1 |
|
85
|
+
|
86
|
+
> Note: The activemodel is an optional dependency, this module [can be enabled](#u-casewith_activemodel_validation---how-to-validate-use-case-attributes) to validate the use cases' attributes.
|
69
87
|
|
70
88
|
## Dependencies
|
71
89
|
|
@@ -73,7 +91,7 @@ The main project goals are:
|
|
73
91
|
|
74
92
|
A simple type system (at runtime) for Ruby.
|
75
93
|
|
76
|
-
|
94
|
+
It is used to validate some internal u-case's methods input. This gem also exposes an [`ActiveModel validator`](https://github.com/serradura/kind#kindvalidator-activemodelvalidations) when requiring the [`u-case/with_activemodel_validation`](#u-casewith_activemodel_validation---how-to-validate-use-case-attributes) module, or when the [`Micro::Case.config`](#microcaseconfig) was used to enable it. Lastly, two type checkers are available through it: [`Kind::Of::Micro::Case`, `Kind::Of::Micro::Case::Result`](https://github.com/serradura/kind#registering-new-custom-type-checker).
|
77
95
|
2. [`u-attributes`](https://github.com/serradura/u-attributes) gem.
|
78
96
|
|
79
97
|
This gem allows defining read-only attributes, that is, your objects will have only getters to access their attributes data.
|
@@ -84,7 +102,7 @@ The main project goals are:
|
|
84
102
|
Add this line to your application's Gemfile:
|
85
103
|
|
86
104
|
```ruby
|
87
|
-
gem 'u-case'
|
105
|
+
gem 'u-case', '~> 3.0.0.rc5'
|
88
106
|
```
|
89
107
|
|
90
108
|
And then execute:
|
@@ -93,7 +111,7 @@ And then execute:
|
|
93
111
|
|
94
112
|
Or install it yourself as:
|
95
113
|
|
96
|
-
$ gem install u-case
|
114
|
+
$ gem install u-case --pre
|
97
115
|
|
98
116
|
## Usage
|
99
117
|
|
@@ -107,45 +125,36 @@ class Multiply < Micro::Case
|
|
107
125
|
# 2. Define the method `call!` with its business logic
|
108
126
|
def call!
|
109
127
|
|
110
|
-
# 3. Wrap the use case
|
128
|
+
# 3. Wrap the use case output using the `Success(result: *)` or `Failure(result: *)` methods
|
111
129
|
if a.is_a?(Numeric) && b.is_a?(Numeric)
|
112
|
-
Success
|
130
|
+
Success result: { number: a * b }
|
113
131
|
else
|
114
|
-
Failure { '`a` and `b` attributes must be numeric' }
|
132
|
+
Failure result: { message: '`a` and `b` attributes must be numeric' }
|
115
133
|
end
|
116
134
|
end
|
117
135
|
end
|
118
136
|
|
119
|
-
|
120
|
-
#
|
121
|
-
|
137
|
+
#========================#
|
138
|
+
# Performing an use case #
|
139
|
+
#========================#
|
122
140
|
|
123
141
|
# Success result
|
124
142
|
|
125
143
|
result = Multiply.call(a: 2, b: 2)
|
126
144
|
|
127
145
|
result.success? # true
|
128
|
-
result.
|
146
|
+
result.data # { number: 4 }
|
129
147
|
|
130
148
|
# Failure result
|
131
149
|
|
132
150
|
bad_result = Multiply.call(a: 2, b: '2')
|
133
151
|
|
134
152
|
bad_result.failure? # true
|
135
|
-
bad_result.
|
136
|
-
|
137
|
-
#-----------------------------#
|
138
|
-
# Calling a use case instance #
|
139
|
-
#-----------------------------#
|
140
|
-
|
141
|
-
result = Multiply.new(a: 2, b: 3).call
|
142
|
-
|
143
|
-
result.value # 6
|
153
|
+
bad_result.data # { message: "`a` and `b` attributes must be numeric" }
|
144
154
|
|
145
155
|
# Note:
|
146
156
|
# ----
|
147
|
-
# The result of a Micro::Case.call
|
148
|
-
# is an instance of Micro::Case::Result
|
157
|
+
# The result of a Micro::Case.call is an instance of Micro::Case::Result
|
149
158
|
```
|
150
159
|
|
151
160
|
[⬆️ Back to Top](#table-of-contents-)
|
@@ -155,28 +164,39 @@ result.value # 6
|
|
155
164
|
A `Micro::Case::Result` stores the use cases output data. These are their main methods:
|
156
165
|
- `#success?` returns true if is a successful result.
|
157
166
|
- `#failure?` returns true if is an unsuccessful result.
|
158
|
-
- `#
|
167
|
+
- `#use_case` returns the use case responsible for it. This feature is handy to handle a flow failure (this topic will be covered ahead).
|
159
168
|
- `#type` a Symbol which gives meaning for the result, this is useful to declare different types of failures or success.
|
160
|
-
- `#
|
161
|
-
- `#
|
162
|
-
- `#
|
169
|
+
- `#data` the result data itself.
|
170
|
+
- `#[]` and `#values_at` are shortcuts to access the `#data` values.
|
171
|
+
- `#key?` returns `true` if the key is present in `#data`.
|
172
|
+
- `#value?` returns `true` if the given value is present in `#data`.
|
173
|
+
- `#slice` returns a new hash that includes only the given keys. If the given keys don't exist, an empty hash is returned.
|
174
|
+
- `#on_success` or `#on_failure` are hook methods that help you to define the application flow.
|
175
|
+
- `#then` this method will allow applying a new use case if the current result was a success. The idea of this feature is to allow the creation of dynamic flows.
|
176
|
+
- `#transitions` returns an array with all of transformations wich a result [has during a flow](#how-to-understand-what-is-happening-during-a-flow-execution).
|
177
|
+
|
178
|
+
> **Note:** for backward compatibility, you could use the `#value` method as an alias of `#data` method.
|
163
179
|
|
164
180
|
[⬆️ Back to Top](#table-of-contents-)
|
165
181
|
|
166
182
|
#### What are the default result types?
|
167
183
|
|
168
|
-
Every result has a type and these are
|
184
|
+
Every result has a type, and these are their default values:
|
169
185
|
- `:ok` when success
|
170
|
-
- `:error
|
186
|
+
- `:error` or `:exception` when failures
|
171
187
|
|
172
188
|
```ruby
|
173
189
|
class Divide < Micro::Case
|
174
190
|
attributes :a, :b
|
175
191
|
|
176
192
|
def call!
|
177
|
-
invalid_attributes.empty?
|
178
|
-
|
179
|
-
|
193
|
+
if invalid_attributes.empty?
|
194
|
+
Success result: { number: a / b }
|
195
|
+
else
|
196
|
+
Failure result: { invalid_attributes: invalid_attributes }
|
197
|
+
end
|
198
|
+
rescue => exception
|
199
|
+
Failure result: exception
|
180
200
|
end
|
181
201
|
|
182
202
|
private def invalid_attributes
|
@@ -189,49 +209,51 @@ end
|
|
189
209
|
result = Divide.call(a: 2, b: 2)
|
190
210
|
|
191
211
|
result.type # :ok
|
192
|
-
result.
|
212
|
+
result.data # { number: 1 }
|
193
213
|
result.success? # true
|
194
|
-
result.use_case #
|
214
|
+
result.use_case # #<Divide:0x0000 @__attributes={"a"=>2, "b"=>2}, @a=2, @b=2, @__result=...>
|
195
215
|
|
196
216
|
# Failure result (type == :error)
|
197
217
|
|
198
218
|
bad_result = Divide.call(a: 2, b: '2')
|
199
219
|
|
200
220
|
bad_result.type # :error
|
201
|
-
bad_result.
|
221
|
+
bad_result.data # { invalid_attributes: { "b"=>"2" } }
|
202
222
|
bad_result.failure? # true
|
203
|
-
bad_result.use_case # #<Divide:0x0000 @__attributes={"a"=>2, "b"=>"2"}, @a=2, @b="2", @__result
|
223
|
+
bad_result.use_case # #<Divide:0x0000 @__attributes={"a"=>2, "b"=>"2"}, @a=2, @b="2", @__result=...>
|
204
224
|
|
205
225
|
# Failure result (type == :exception)
|
206
226
|
|
207
227
|
err_result = Divide.call(a: 2, b: 0)
|
208
228
|
|
209
229
|
err_result.type # :exception
|
210
|
-
err_result.
|
230
|
+
err_result.data # { exception: <ZeroDivisionError: divided by 0> }
|
211
231
|
err_result.failure? # true
|
212
|
-
err_result.use_case # #<Divide:0x0000 @__attributes={"a"=>2, "b"=>0}, @a=2, @b=0, @__result=#<Micro::Case::Result:0x0000 @use_case=#<Divide:0x0000 ...>, @type=:exception, @value=#<ZeroDivisionError: divided by 0>, @success=false
|
232
|
+
err_result.use_case # #<Divide:0x0000 @__attributes={"a"=>2, "b"=>0}, @a=2, @b=0, @__result=#<Micro::Case::Result:0x0000 @use_case=#<Divide:0x0000 ...>, @type=:exception, @value=#<ZeroDivisionError: divided by 0>, @success=false>
|
213
233
|
|
214
234
|
# Note:
|
215
235
|
# ----
|
216
236
|
# Any Exception instance which is wrapped by
|
217
|
-
# the Failure() method will receive `:exception` instead of the `:error` type.
|
237
|
+
# the Failure(result: *) method will receive `:exception` instead of the `:error` type.
|
218
238
|
```
|
219
239
|
|
220
240
|
[⬆️ Back to Top](#table-of-contents-)
|
221
241
|
|
222
242
|
#### How to define custom result types?
|
223
243
|
|
224
|
-
Answer: Use a symbol as the argument of `Success()`, `Failure()` methods and declare
|
244
|
+
Answer: Use a symbol as the argument of `Success()`, `Failure()` methods and declare the `result:` keyword to set the result data.
|
225
245
|
|
226
246
|
```ruby
|
227
247
|
class Multiply < Micro::Case
|
228
248
|
attributes :a, :b
|
229
249
|
|
230
250
|
def call!
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
251
|
+
if a.is_a?(Numeric) && b.is_a?(Numeric)
|
252
|
+
Success result: { number: a * b }
|
253
|
+
else
|
254
|
+
Failure :invalid_data, result: {
|
255
|
+
attributes: attributes.reject { |_, input| input.is_a?(Numeric) }
|
256
|
+
}
|
235
257
|
end
|
236
258
|
end
|
237
259
|
end
|
@@ -241,7 +263,7 @@ end
|
|
241
263
|
result = Multiply.call(a: 3, b: 2)
|
242
264
|
|
243
265
|
result.type # :ok
|
244
|
-
result.
|
266
|
+
result.data # { number: 6 }
|
245
267
|
result.success? # true
|
246
268
|
|
247
269
|
# Failure result
|
@@ -249,31 +271,33 @@ result.success? # true
|
|
249
271
|
bad_result = Multiply.call(a: 3, b: '2')
|
250
272
|
|
251
273
|
bad_result.type # :invalid_data
|
252
|
-
bad_result.
|
274
|
+
bad_result.data # { attributes: {"b"=>"2"} }
|
253
275
|
bad_result.failure? # true
|
254
276
|
```
|
255
277
|
|
256
278
|
[⬆️ Back to Top](#table-of-contents-)
|
257
279
|
|
258
|
-
#### Is it possible to define a custom
|
280
|
+
#### Is it possible to define a custom type without a result data?
|
259
281
|
|
260
|
-
Answer: Yes, it is. But
|
282
|
+
Answer: Yes, it is possible. But this will have special behavior because the result data will be a hash with the given type as the key and `true` as its value.
|
261
283
|
|
262
284
|
```ruby
|
263
285
|
class Multiply < Micro::Case
|
264
286
|
attributes :a, :b
|
265
287
|
|
266
288
|
def call!
|
267
|
-
|
268
|
-
|
269
|
-
|
289
|
+
if a.is_a?(Numeric) && b.is_a?(Numeric)
|
290
|
+
Success result: { number: a * b }
|
291
|
+
else
|
292
|
+
Failure(:invalid_data)
|
293
|
+
end
|
270
294
|
end
|
271
295
|
end
|
272
296
|
|
273
297
|
result = Multiply.call(a: 2, b: '2')
|
274
298
|
|
275
299
|
result.failure? # true
|
276
|
-
result.
|
300
|
+
result.data # { :invalid_data => true }
|
277
301
|
result.type # :invalid_data
|
278
302
|
result.use_case.attributes # {"a"=>2, "b"=>"2"}
|
279
303
|
|
@@ -287,7 +311,7 @@ result.use_case.attributes # {"a"=>2, "b"=>"2"}
|
|
287
311
|
|
288
312
|
#### How to use the result hooks?
|
289
313
|
|
290
|
-
As mentioned earlier, the `Micro::Case::Result` has two methods to improve the flow control. They are: `#on_success`, `on_failure`.
|
314
|
+
As [mentioned earlier](#microcaseresult---what-is-a-use-case-result), the `Micro::Case::Result` has two methods to improve the application flow control. They are: `#on_success`, `on_failure`.
|
291
315
|
|
292
316
|
The examples below show how to use them:
|
293
317
|
|
@@ -296,10 +320,10 @@ class Double < Micro::Case
|
|
296
320
|
attribute :number
|
297
321
|
|
298
322
|
def call!
|
299
|
-
return Failure
|
300
|
-
return Failure
|
323
|
+
return Failure :invalid, result: { msg: 'number must be a numeric value' } unless number.is_a?(Numeric)
|
324
|
+
return Failure :lte_zero, result: { msg: 'number must be greater than 0' } if number <= 0
|
301
325
|
|
302
|
-
Success
|
326
|
+
Success result: { number: number * 2 }
|
303
327
|
end
|
304
328
|
end
|
305
329
|
|
@@ -309,11 +333,11 @@ end
|
|
309
333
|
|
310
334
|
Double
|
311
335
|
.call(number: 3)
|
312
|
-
.on_success { |
|
313
|
-
.on_failure(:invalid) { |
|
314
|
-
.on_failure(:lte_zero) { |
|
336
|
+
.on_success { |result| p result[:number] }
|
337
|
+
.on_failure(:invalid) { |result| raise TypeError, result[:msg] }
|
338
|
+
.on_failure(:lte_zero) { |result| raise ArgumentError, result[:msg] }
|
315
339
|
|
316
|
-
# The output
|
340
|
+
# The output will be:
|
317
341
|
# 6
|
318
342
|
|
319
343
|
#=============================#
|
@@ -322,25 +346,24 @@ Double
|
|
322
346
|
|
323
347
|
Double
|
324
348
|
.call(number: -1)
|
325
|
-
.on_success { |
|
349
|
+
.on_success { |result| p result[:number] }
|
326
350
|
.on_failure { |_result, use_case| puts "#{use_case.class.name} was the use case responsible for the failure" }
|
327
|
-
.on_failure(:invalid) { |
|
328
|
-
.on_failure(:lte_zero) { |
|
351
|
+
.on_failure(:invalid) { |result| raise TypeError, result[:msg] }
|
352
|
+
.on_failure(:lte_zero) { |result| raise ArgumentError, result[:msg] }
|
329
353
|
|
330
354
|
# The outputs will be:
|
331
355
|
#
|
332
|
-
# 1.
|
333
|
-
# 2.
|
356
|
+
# 1. It will print the message: Double was the use case responsible for the failure
|
357
|
+
# 2. It will raise the exception: ArgumentError (the number must be greater than 0)
|
334
358
|
|
335
359
|
# Note:
|
336
360
|
# ----
|
337
|
-
# The use case responsible for the
|
361
|
+
# The use case responsible for the result will always be accessible as the second hook argument
|
338
362
|
```
|
339
363
|
|
340
|
-
#### Why the
|
364
|
+
#### Why the hook usage without a defined type exposes the result itself?
|
341
365
|
|
342
|
-
Answer: To allow you to define how to handle the program flow using some
|
343
|
-
conditional statement (like an `if`, `case/when`).
|
366
|
+
Answer: To allow you to define how to handle the program flow using some conditional statement like an `if` or `case when`.
|
344
367
|
|
345
368
|
```ruby
|
346
369
|
class Double < Micro::Case
|
@@ -348,46 +371,43 @@ class Double < Micro::Case
|
|
348
371
|
|
349
372
|
def call!
|
350
373
|
return Failure(:invalid) unless number.is_a?(Numeric)
|
351
|
-
return Failure
|
374
|
+
return Failure :lte_zero, result: attributes(:number) if number <= 0
|
352
375
|
|
353
|
-
Success
|
376
|
+
Success result: { number: number * 2 }
|
354
377
|
end
|
355
378
|
end
|
356
379
|
|
357
|
-
#=================================#
|
358
|
-
# Using the result type and value #
|
359
|
-
#=================================#
|
360
|
-
|
361
380
|
Double
|
362
|
-
.call(-1)
|
381
|
+
.call(number: -1)
|
363
382
|
.on_failure do |result, use_case|
|
364
383
|
case result.type
|
365
|
-
when :invalid then raise TypeError,
|
366
|
-
when :lte_zero then raise ArgumentError, "
|
384
|
+
when :invalid then raise TypeError, "number must be a numeric value"
|
385
|
+
when :lte_zero then raise ArgumentError, "number `#{result[:number]}` must be greater than 0"
|
367
386
|
else raise NotImplementedError
|
368
387
|
end
|
369
388
|
end
|
370
389
|
|
371
|
-
# The output will be
|
390
|
+
# The output will be an exception:
|
372
391
|
#
|
373
|
-
# ArgumentError (
|
392
|
+
# ArgumentError (number `-1` must be greater than 0)
|
393
|
+
```
|
374
394
|
|
375
|
-
|
376
|
-
# Using decomposition to access result value and type #
|
377
|
-
#=====================================================#
|
395
|
+
> **Note:** The same that was did in the previous examples could be done with `#on_success` hook!
|
378
396
|
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
#
|
383
|
-
|
397
|
+
##### Using decomposition to access the result data and type
|
398
|
+
|
399
|
+
The syntax to decompose an Array can be used in assignments and in method/block arguments.
|
400
|
+
If you doesn't know it, check out the [Ruby doc](https://ruby-doc.org/core-2.2.0/doc/syntax/assignment_rdoc.html#label-Array+Decomposition).
|
401
|
+
|
402
|
+
```ruby
|
403
|
+
# The object exposed in the hook without a type is a Micro::Case::Result and it can be decomposed. e.g:
|
384
404
|
|
385
405
|
Double
|
386
|
-
.call(-2)
|
387
|
-
.on_failure do |(
|
406
|
+
.call(number: -2)
|
407
|
+
.on_failure do |(data, type), use_case|
|
388
408
|
case type
|
389
|
-
when :invalid then raise TypeError, '
|
390
|
-
when :lte_zero then raise ArgumentError, "
|
409
|
+
when :invalid then raise TypeError, 'number must be a numeric value'
|
410
|
+
when :lte_zero then raise ArgumentError, "number `#{data[:number]}` must be greater than 0"
|
391
411
|
else raise NotImplementedError
|
392
412
|
end
|
393
413
|
end
|
@@ -397,6 +417,8 @@ Double
|
|
397
417
|
# ArgumentError (the number `-2` must be greater than 0)
|
398
418
|
```
|
399
419
|
|
420
|
+
> **Note:** The same that was did in the previous examples could be done with `#on_success` hook!
|
421
|
+
|
400
422
|
[⬆️ Back to Top](#table-of-contents-)
|
401
423
|
|
402
424
|
#### What happens if a result hook was declared multiple times?
|
@@ -408,41 +430,43 @@ class Double < Micro::Case
|
|
408
430
|
attributes :number
|
409
431
|
|
410
432
|
def call!
|
411
|
-
|
412
|
-
|
413
|
-
|
433
|
+
if number.is_a?(Numeric)
|
434
|
+
Success :computed, result: { number: number * 2 }
|
435
|
+
else
|
436
|
+
Failure :invalid, result: { msg: 'number must be a numeric value' }
|
437
|
+
end
|
414
438
|
end
|
415
439
|
end
|
416
440
|
|
417
441
|
result = Double.call(number: 3)
|
418
|
-
result.
|
419
|
-
result
|
442
|
+
result.data # { number: 6 }
|
443
|
+
result[:number] * 4 # 24
|
420
444
|
|
421
445
|
accum = 0
|
422
446
|
|
423
|
-
result
|
424
|
-
|
425
|
-
|
426
|
-
|
447
|
+
result
|
448
|
+
.on_success { |result| accum += result[:number] }
|
449
|
+
.on_success { |result| accum += result[:number] }
|
450
|
+
.on_success(:computed) { |result| accum += result[:number] }
|
451
|
+
.on_success(:computed) { |result| accum += result[:number] }
|
427
452
|
|
428
453
|
accum # 24
|
429
454
|
|
430
|
-
result
|
455
|
+
result[:number] * 4 == accum # true
|
431
456
|
```
|
432
457
|
|
433
458
|
#### How to use the `Micro::Case::Result#then` method?
|
434
459
|
|
435
|
-
This method allows you to create dynamic flows, so, with it,
|
436
|
-
you can add new use cases or flows to continue the result transformation. e.g:
|
460
|
+
This method allows you to create dynamic flows, so, with it, you can add new use cases or flows to continue the result transformation. e.g:
|
437
461
|
|
438
462
|
```ruby
|
439
463
|
class ForbidNegativeNumber < Micro::Case
|
440
464
|
attribute :number
|
441
465
|
|
442
466
|
def call!
|
443
|
-
return Success
|
467
|
+
return Success result: attributes if number >= 0
|
444
468
|
|
445
|
-
Failure
|
469
|
+
Failure result: attributes
|
446
470
|
end
|
447
471
|
end
|
448
472
|
|
@@ -450,7 +474,7 @@ class Add3 < Micro::Case
|
|
450
474
|
attribute :number
|
451
475
|
|
452
476
|
def call!
|
453
|
-
Success
|
477
|
+
Success result: { number: number + 3 }
|
454
478
|
end
|
455
479
|
end
|
456
480
|
|
@@ -459,7 +483,7 @@ result1 =
|
|
459
483
|
.call(number: -1)
|
460
484
|
.then(Add3)
|
461
485
|
|
462
|
-
result1.
|
486
|
+
result1.data # {'number' => -1}
|
463
487
|
result1.failure? # true
|
464
488
|
|
465
489
|
# ---
|
@@ -469,7 +493,7 @@ result2 =
|
|
469
493
|
.call(number: 1)
|
470
494
|
.then(Add3)
|
471
495
|
|
472
|
-
result2.
|
496
|
+
result2.data # {'number' => 4}
|
473
497
|
result2.success? # true
|
474
498
|
```
|
475
499
|
|
@@ -477,6 +501,44 @@ result2.success? # true
|
|
477
501
|
|
478
502
|
[⬆️ Back to Top](#table-of-contents-)
|
479
503
|
|
504
|
+
##### What does happens when a `Micro::Case::Result#then` receives a block?
|
505
|
+
|
506
|
+
It will yields self (a `Micro::Case::Result` instance) to the block, and will return the output of the block instead of itself. e.g:
|
507
|
+
|
508
|
+
```ruby
|
509
|
+
class Add < Micro::Case
|
510
|
+
attributes :a, :b
|
511
|
+
|
512
|
+
def call!
|
513
|
+
if Kind.of?(Numeric, a, b)
|
514
|
+
Success result: { sum: a + b }
|
515
|
+
else
|
516
|
+
Failure(:attributes_arent_numbers)
|
517
|
+
end
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
# --
|
522
|
+
|
523
|
+
success_result =
|
524
|
+
Add
|
525
|
+
.call(a: 2, b: 2)
|
526
|
+
.then { |result| result.success? ? result[:sum] : 0 }
|
527
|
+
|
528
|
+
puts success_result # 4
|
529
|
+
|
530
|
+
# --
|
531
|
+
|
532
|
+
failure_result =
|
533
|
+
Add
|
534
|
+
.call(a: 2, b: '2')
|
535
|
+
.then { |result| result.success? ? result[:sum] : 0 }
|
536
|
+
|
537
|
+
puts failure_result # 0
|
538
|
+
```
|
539
|
+
|
540
|
+
[⬆️ Back to Top](#table-of-contents-)
|
541
|
+
|
480
542
|
##### How to make attributes data injection using this feature?
|
481
543
|
|
482
544
|
Pass a Hash as the second argument of the `Micro::Case::Result#then` method.
|
@@ -491,10 +553,9 @@ Todo::FindAllForUser
|
|
491
553
|
|
492
554
|
[⬆️ Back to Top](#table-of-contents-)
|
493
555
|
|
494
|
-
### `Micro::
|
556
|
+
### `Micro::Cases::Flow` - How to compose use cases?
|
495
557
|
|
496
|
-
|
497
|
-
The main idea of this feature is to use/reuse use cases as steps of a new use case.
|
558
|
+
We call as **flow** a composition of use cases. The main idea of this feature is to use/reuse use cases as steps of a new use case. e.g.
|
498
559
|
|
499
560
|
```ruby
|
500
561
|
module Steps
|
@@ -503,9 +564,9 @@ module Steps
|
|
503
564
|
|
504
565
|
def call!
|
505
566
|
if numbers.all? { |value| String(value) =~ /\d+/ }
|
506
|
-
Success
|
567
|
+
Success result: { numbers: numbers.map(&:to_i) }
|
507
568
|
else
|
508
|
-
Failure
|
569
|
+
Failure result: { message: 'numbers must contain only numeric types' }
|
509
570
|
end
|
510
571
|
end
|
511
572
|
end
|
@@ -514,7 +575,7 @@ module Steps
|
|
514
575
|
attribute :numbers
|
515
576
|
|
516
577
|
def call!
|
517
|
-
Success
|
578
|
+
Success result: { numbers: numbers.map { |number| number + 2 } }
|
518
579
|
end
|
519
580
|
end
|
520
581
|
|
@@ -522,7 +583,7 @@ module Steps
|
|
522
583
|
attribute :numbers
|
523
584
|
|
524
585
|
def call!
|
525
|
-
Success
|
586
|
+
Success result: { numbers: numbers.map { |number| number * 2 } }
|
526
587
|
end
|
527
588
|
end
|
528
589
|
|
@@ -530,73 +591,45 @@ module Steps
|
|
530
591
|
attribute :numbers
|
531
592
|
|
532
593
|
def call!
|
533
|
-
Success
|
594
|
+
Success result: { numbers: numbers.map { |number| number * number } }
|
534
595
|
end
|
535
596
|
end
|
536
597
|
end
|
537
598
|
|
538
|
-
|
539
|
-
# Creating a flow using
|
540
|
-
|
599
|
+
#-------------------------------------------#
|
600
|
+
# Creating a flow using Micro::Cases.flow() #
|
601
|
+
#-------------------------------------------#
|
541
602
|
|
542
|
-
Add2ToAllNumbers = Micro::
|
603
|
+
Add2ToAllNumbers = Micro::Cases.flow([
|
543
604
|
Steps::ConvertTextToNumbers,
|
544
605
|
Steps::Add2
|
545
606
|
])
|
546
607
|
|
547
608
|
result = Add2ToAllNumbers.call(numbers: %w[1 1 2 2 3 4])
|
548
609
|
|
549
|
-
|
550
|
-
|
610
|
+
result.success? # true
|
611
|
+
result.data # {:numbers => [3, 3, 4, 4, 5, 6]}
|
551
612
|
|
552
|
-
|
553
|
-
#
|
554
|
-
|
613
|
+
#-------------------------------#
|
614
|
+
# Creating a flow using classes #
|
615
|
+
#-------------------------------#
|
555
616
|
|
556
617
|
class DoubleAllNumbers < Micro::Case
|
557
618
|
flow Steps::ConvertTextToNumbers,
|
558
619
|
Steps::Double
|
559
620
|
end
|
560
621
|
|
561
|
-
DoubleAllNumbers
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
# !------------------------------------ ! #
|
566
|
-
# ! Deprecated: Micro::Case::Flow mixin ! #
|
567
|
-
# !-------------------------------------! #
|
568
|
-
|
569
|
-
# The code below still works, but it will output a warning message:
|
570
|
-
# Deprecation: Micro::Case::Flow mixin is being deprecated, please use `Micro::Case` inheritance instead.
|
571
|
-
|
572
|
-
class DoubleAllNumbers
|
573
|
-
include Micro::Case::Flow
|
574
|
-
|
575
|
-
flow Steps::ConvertTextToNumbers,
|
576
|
-
Steps::Double
|
577
|
-
end
|
578
|
-
|
579
|
-
# Note: This feature will be removed in the next major release (3.0)
|
580
|
-
|
581
|
-
#-------------------------------------------------------------#
|
582
|
-
# Another way to create a flow using the composition operator #
|
583
|
-
#-------------------------------------------------------------#
|
584
|
-
|
585
|
-
SquareAllNumbers =
|
586
|
-
Steps::ConvertTextToNumbers >> Steps::Square
|
587
|
-
|
588
|
-
SquareAllNumbers
|
589
|
-
.call(numbers: %w[1 1 2 2 3 4])
|
590
|
-
.on_success { |value| p value[:numbers] } # [1, 1, 4, 4, 9, 16]
|
622
|
+
DoubleAllNumbers.
|
623
|
+
call(numbers: %w[1 1 b 2 3 4]).
|
624
|
+
on_failure { |result| puts result[:message] } # "numbers must contain only numeric types"
|
625
|
+
```
|
591
626
|
|
592
|
-
|
593
|
-
# ----
|
594
|
-
# When happening a failure, the use case responsible
|
595
|
-
# will be accessible in the result
|
627
|
+
When happening a failure, the use case responsible will be accessible in the result.
|
596
628
|
|
597
|
-
|
629
|
+
```ruby
|
630
|
+
result = DoubleAllNumbers.call(numbers: %w[1 1 b 2 3 4])
|
598
631
|
|
599
|
-
result.failure?
|
632
|
+
result.failure? # true
|
600
633
|
result.use_case.is_a?(Steps::ConvertTextToNumbers) # true
|
601
634
|
|
602
635
|
result.on_failure do |_message, use_case|
|
@@ -606,9 +639,9 @@ end
|
|
606
639
|
|
607
640
|
[⬆️ Back to Top](#table-of-contents-)
|
608
641
|
|
609
|
-
#### Is it possible to compose a
|
642
|
+
#### Is it possible to compose a flow with other flows?
|
610
643
|
|
611
|
-
Answer: Yes, it is.
|
644
|
+
Answer: Yes, it is possible.
|
612
645
|
|
613
646
|
```ruby
|
614
647
|
module Steps
|
@@ -617,9 +650,9 @@ module Steps
|
|
617
650
|
|
618
651
|
def call!
|
619
652
|
if numbers.all? { |value| String(value) =~ /\d+/ }
|
620
|
-
Success
|
653
|
+
Success result: { numbers: numbers.map(&:to_i) }
|
621
654
|
else
|
622
|
-
Failure
|
655
|
+
Failure result: { message: 'numbers must contain only numeric types' }
|
623
656
|
end
|
624
657
|
end
|
625
658
|
end
|
@@ -628,7 +661,7 @@ module Steps
|
|
628
661
|
attribute :numbers
|
629
662
|
|
630
663
|
def call!
|
631
|
-
Success
|
664
|
+
Success result: { numbers: numbers.map { |number| number + 2 } }
|
632
665
|
end
|
633
666
|
end
|
634
667
|
|
@@ -636,7 +669,7 @@ module Steps
|
|
636
669
|
attribute :numbers
|
637
670
|
|
638
671
|
def call!
|
639
|
-
Success
|
672
|
+
Success result: { numbers: numbers.map { |number| number * 2 } }
|
640
673
|
end
|
641
674
|
end
|
642
675
|
|
@@ -644,47 +677,55 @@ module Steps
|
|
644
677
|
attribute :numbers
|
645
678
|
|
646
679
|
def call!
|
647
|
-
Success
|
680
|
+
Success result: { numbers: numbers.map { |number| number * number } }
|
648
681
|
end
|
649
682
|
end
|
650
683
|
end
|
651
684
|
|
652
|
-
|
653
|
-
|
654
|
-
SquareAllNumbers = Steps::ConvertTextToNumbers >> Steps::Square
|
685
|
+
DoubleAllNumbers =
|
686
|
+
Micro::Cases.flow([Steps::ConvertTextToNumbers, Steps::Double])
|
655
687
|
|
656
|
-
|
657
|
-
|
688
|
+
SquareAllNumbers =
|
689
|
+
Micro::Cases.flow([Steps::ConvertTextToNumbers, Steps::Square])
|
658
690
|
|
659
|
-
|
660
|
-
|
691
|
+
DoubleAllNumbersAndAdd2 =
|
692
|
+
Micro::Cases.flow([DoubleAllNumbers, Steps::Add2])
|
693
|
+
|
694
|
+
SquareAllNumbersAndAdd2 =
|
695
|
+
Micro::Cases.flow([SquareAllNumbers, Steps::Add2])
|
696
|
+
|
697
|
+
SquareAllNumbersAndDouble =
|
698
|
+
Micro::Cases.flow([SquareAllNumbersAndAdd2, DoubleAllNumbers])
|
699
|
+
|
700
|
+
DoubleAllNumbersAndSquareAndAdd2 =
|
701
|
+
Micro::Cases.flow([DoubleAllNumbers, SquareAllNumbersAndAdd2])
|
661
702
|
|
662
703
|
SquareAllNumbersAndDouble
|
663
704
|
.call(numbers: %w[1 1 2 2 3 4])
|
664
|
-
.on_success { |
|
705
|
+
.on_success { |result| p result[:numbers] } # [6, 6, 12, 12, 22, 36]
|
665
706
|
|
666
707
|
DoubleAllNumbersAndSquareAndAdd2
|
667
708
|
.call(numbers: %w[1 1 2 2 3 4])
|
668
|
-
.on_success { |
|
709
|
+
.on_success { |result| p result[:numbers] } # [6, 6, 18, 18, 38, 66]
|
669
710
|
```
|
670
711
|
|
671
|
-
Note
|
712
|
+
> **Note:** You can blend any [approach](#microcasesflow---how-to-compose-use-cases) to create use case flows - [examples](https://github.com/serradura/u-case/blob/714c6b658fc6aa02617e6833ddee09eddc760f2a/test/micro/cases/flow/blend_test.rb#L5-L35).
|
672
713
|
|
673
714
|
[⬆️ Back to Top](#table-of-contents-)
|
674
715
|
|
675
716
|
#### Is it possible a flow accumulates its input and merges each success result to use as the argument of the next use cases?
|
676
717
|
|
677
|
-
Answer: Yes, it is! Look at the example below to understand how the data accumulation works inside of
|
718
|
+
Answer: Yes, it is possible! Look at the example below to understand how the data accumulation works inside of a flow execution.
|
678
719
|
|
679
720
|
```ruby
|
680
721
|
module Users
|
681
|
-
class
|
722
|
+
class FindByEmail < Micro::Case
|
682
723
|
attribute :email
|
683
724
|
|
684
725
|
def call!
|
685
726
|
user = User.find_by(email: email)
|
686
727
|
|
687
|
-
return Success
|
728
|
+
return Success result: { user: user } if user
|
688
729
|
|
689
730
|
Failure(:user_not_found)
|
690
731
|
end
|
@@ -699,14 +740,14 @@ module Users
|
|
699
740
|
return Failure(:user_must_be_persisted) if user.new_record?
|
700
741
|
return Failure(:wrong_password) if user.wrong_password?(password)
|
701
742
|
|
702
|
-
return Success
|
743
|
+
return Success result: attributes(:user)
|
703
744
|
end
|
704
745
|
end
|
705
746
|
end
|
706
747
|
|
707
748
|
module Users
|
708
|
-
Authenticate = Micro::
|
709
|
-
|
749
|
+
Authenticate = Micro::Cases.flow([
|
750
|
+
FindByEmail,
|
710
751
|
ValidatePassword
|
711
752
|
])
|
712
753
|
end
|
@@ -714,14 +755,14 @@ end
|
|
714
755
|
Users::Authenticate
|
715
756
|
.call(email: 'somebody@test.com', password: 'password')
|
716
757
|
.on_success { |result| sign_in(result[:user]) }
|
717
|
-
.on_failure(:wrong_password) {
|
718
|
-
.on_failure(:user_not_found) {
|
758
|
+
.on_failure(:wrong_password) { render status: 401 }
|
759
|
+
.on_failure(:user_not_found) { render status: 404 }
|
719
760
|
```
|
720
761
|
|
721
|
-
First,
|
762
|
+
First, let's see the attributes used by each use case:
|
722
763
|
|
723
764
|
```ruby
|
724
|
-
class Users::
|
765
|
+
class Users::FindByEmail < Micro::Case
|
725
766
|
attribute :email
|
726
767
|
end
|
727
768
|
|
@@ -731,14 +772,13 @@ end
|
|
731
772
|
```
|
732
773
|
|
733
774
|
As you can see the `Users::ValidatePassword` expects a user as its input. So, how does it receives the user?
|
734
|
-
It receives the user from the `Users::
|
775
|
+
Answer: It receives the user from the `Users::FindByEmail` success result!
|
735
776
|
|
736
|
-
And this
|
737
|
-
of one flow will compose the input of the next use case in the flow!
|
777
|
+
And this is the power of use cases composition because the output of one step will compose the input of the next use case in the flow!
|
738
778
|
|
739
779
|
> input **>>** process **>>** output
|
740
780
|
|
741
|
-
> **Note:** Check out these test examples [Micro::
|
781
|
+
> **Note:** Check out these test examples [Micro::Cases::Flow](https://github.com/serradura/u-case/blob/c96a3650469da40dc9f83ff678204055b7015d01/test/micro/cases/flow/result_transitions_test.rb) and [Micro::Cases::Safe::Flow](https://github.com/serradura/u-case/blob/c96a3650469da40dc9f83ff678204055b7015d01/test/micro/cases/safe/flow/result_transitions_test.rb) to see different use cases having access to the data in a flow.
|
742
782
|
|
743
783
|
[⬆️ Back to Top](#table-of-contents-)
|
744
784
|
|
@@ -756,12 +796,12 @@ user_authenticated.transitions
|
|
756
796
|
[
|
757
797
|
{
|
758
798
|
:use_case => {
|
759
|
-
:class => Users::
|
799
|
+
:class => Users::FindByEmail,
|
760
800
|
:attributes => { :email => "rodrigo@test.com" }
|
761
801
|
},
|
762
802
|
:success => {
|
763
803
|
:type => :ok,
|
764
|
-
:
|
804
|
+
:result => {
|
765
805
|
:user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
|
766
806
|
}
|
767
807
|
},
|
@@ -777,7 +817,7 @@ user_authenticated.transitions
|
|
777
817
|
},
|
778
818
|
:success => {
|
779
819
|
:type => :ok,
|
780
|
-
:
|
820
|
+
:result => {
|
781
821
|
:user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
|
782
822
|
}
|
783
823
|
},
|
@@ -787,14 +827,12 @@ user_authenticated.transitions
|
|
787
827
|
```
|
788
828
|
|
789
829
|
The example above shows the output generated by the `Micro::Case::Result#transitions`.
|
790
|
-
With it is possible to analyze the use cases execution order and what were the given `inputs` (attributes) and `outputs` (`success
|
830
|
+
With it is possible to analyze the use cases' execution order and what were the given `inputs` (`[:attributes]`) and `outputs` (`[:success][:result]`) in the entire execution.
|
791
831
|
|
792
|
-
And look up the `accessible_attributes` property,
|
832
|
+
And look up the `accessible_attributes` property, it shows whats attributes are accessible in that flow step. For example, in the last step, you can see that the `accessible_attributes` increased because of the [data flow accumulation](#is-it-possible-a-flow-accumulates-its-input-and-merges-each-success-result-to-use-as-the-argument-of-the-next-use-cases).
|
793
833
|
|
794
834
|
> **Note:** The [`Micro::Case::Result#then`](#how-to-use-the-microcaseresultthen-method) increments the `Micro::Case::Result#transitions`.
|
795
835
|
|
796
|
-
PS: Use the `Micro::Case::Result.disable_transition_tracking` global feature toggle to disable this feature (use once) and increase the use cases' performance.
|
797
|
-
|
798
836
|
##### `Micro::Case::Result#transitions` schema
|
799
837
|
```ruby
|
800
838
|
[
|
@@ -806,25 +844,29 @@ PS: Use the `Micro::Case::Result.disable_transition_tracking` global feature tog
|
|
806
844
|
[success:, failure:] => { # (Output)
|
807
845
|
type: <Symbol>, # Result type. Defaults:
|
808
846
|
# Success = :ok, Failure = :error/:exception
|
809
|
-
|
847
|
+
result: <Hash> # The data returned by the use case result
|
810
848
|
},
|
811
849
|
accessible_attributes: <Array>, # Properties that can be accessed by the use case's attributes,
|
812
|
-
#
|
813
|
-
# with
|
850
|
+
# it starts with Hash used to invoke it and that will be incremented
|
851
|
+
# with the result values of each use case in the flow.
|
814
852
|
}
|
815
853
|
]
|
816
854
|
```
|
817
855
|
|
818
|
-
|
856
|
+
##### Is it possible disable the `Micro::Case::Result#transitions`?
|
819
857
|
|
820
|
-
Answer: Yes, it is! You can use the `
|
858
|
+
Answer: Yes, it is! You can use the `Micro::Case.config` to do this. [Link to](#microcaseconfig) this section.
|
859
|
+
|
860
|
+
#### Is it possible to declare a flow that includes the use case itself as a step?
|
861
|
+
|
862
|
+
Answer: Yes, it is! You can use `self` or the `self.call!` macro. e.g:
|
821
863
|
|
822
864
|
```ruby
|
823
865
|
class ConvertTextToNumber < Micro::Case
|
824
866
|
attribute :text
|
825
867
|
|
826
868
|
def call!
|
827
|
-
Success
|
869
|
+
Success result: { number: text.to_i }
|
828
870
|
end
|
829
871
|
end
|
830
872
|
|
@@ -832,7 +874,7 @@ class ConvertNumberToText < Micro::Case
|
|
832
874
|
attribute :number
|
833
875
|
|
834
876
|
def call!
|
835
|
-
Success
|
877
|
+
Success result: { text: number.to_s }
|
836
878
|
end
|
837
879
|
end
|
838
880
|
|
@@ -844,37 +886,36 @@ class Double < Micro::Case
|
|
844
886
|
attribute :number
|
845
887
|
|
846
888
|
def call!
|
847
|
-
Success
|
889
|
+
Success result: { number: number * 2 }
|
848
890
|
end
|
849
891
|
end
|
850
892
|
|
851
893
|
result = Double.call(text: '4')
|
852
894
|
|
853
895
|
result.success? # true
|
854
|
-
result
|
855
|
-
|
856
|
-
# NOTE: This feature can be used with the Micro::Case::Safe.
|
857
|
-
# Checkout the test: test/micro/case/safe/flow/with_classes/using_itself_test.rb
|
896
|
+
result[:number] # "8"
|
858
897
|
```
|
859
898
|
|
899
|
+
> **Note:** This feature can be used with the Micro::Case::Safe. Checkout this test to see an example: https://github.com/serradura/u-case/blob/714c6b658fc6aa02617e6833ddee09eddc760f2a/test/micro/case/safe/with_inner_flow_test.rb
|
900
|
+
|
860
901
|
[⬆️ Back to Top](#table-of-contents-)
|
861
902
|
|
862
903
|
### `Micro::Case::Strict` - What is a strict use case?
|
863
904
|
|
864
|
-
Answer:
|
905
|
+
Answer: it is a kind of use case that will require all the keywords (attributes) on its initialization.
|
865
906
|
|
866
907
|
```ruby
|
867
908
|
class Double < Micro::Case::Strict
|
868
909
|
attribute :numbers
|
869
910
|
|
870
911
|
def call!
|
871
|
-
Success
|
912
|
+
Success result: { numbers: numbers.map { |number| number * 2 } }
|
872
913
|
end
|
873
914
|
end
|
874
915
|
|
875
916
|
Double.call({})
|
876
917
|
|
877
|
-
# The output will be
|
918
|
+
# The output will be:
|
878
919
|
# ArgumentError (missing keyword: :numbers)
|
879
920
|
```
|
880
921
|
|
@@ -882,11 +923,7 @@ Double.call({})
|
|
882
923
|
|
883
924
|
### `Micro::Case::Safe` - Is there some feature to auto handle exceptions inside of a use case or flow?
|
884
925
|
|
885
|
-
|
886
|
-
|
887
|
-
**Use cases:**
|
888
|
-
|
889
|
-
Like `Micro::Case::Strict` the `Micro::Case::Safe` is another kind of use case. It has the ability to auto intercept any exception as a failure result. e.g:
|
926
|
+
Yes, there is one! Like `Micro::Case::Strict` the `Micro::Case::Safe` is another kind of use case. It has the ability to auto intercept any exception as a failure result. e.g:
|
890
927
|
|
891
928
|
```ruby
|
892
929
|
require 'logger'
|
@@ -897,64 +934,57 @@ class Divide < Micro::Case::Safe
|
|
897
934
|
attributes :a, :b
|
898
935
|
|
899
936
|
def call!
|
900
|
-
|
901
|
-
|
937
|
+
if a.is_a?(Integer) && b.is_a?(Integer)
|
938
|
+
Success result: { number: a / b}
|
939
|
+
else
|
940
|
+
Failure(:not_an_integer)
|
941
|
+
end
|
902
942
|
end
|
903
943
|
end
|
904
944
|
|
905
945
|
result = Divide.call(a: 2, b: 0)
|
906
|
-
result.type == :exception
|
907
|
-
result.
|
946
|
+
result.type == :exception # true
|
947
|
+
result.data # { exception: #<ZeroDivisionError...> }
|
948
|
+
result[:exception].is_a?(ZeroDivisionError) # true
|
908
949
|
|
909
|
-
result.on_failure(:exception) do |
|
910
|
-
AppLogger.error(exception.message) # E, [2019-08-21T00:05:44.195506 #9532] ERROR -- : divided by 0
|
950
|
+
result.on_failure(:exception) do |result|
|
951
|
+
AppLogger.error(result[:exception].message) # E, [2019-08-21T00:05:44.195506 #9532] ERROR -- : divided by 0
|
911
952
|
end
|
953
|
+
```
|
912
954
|
|
913
|
-
|
914
|
-
# ----
|
915
|
-
# If you need to handle a specific error,
|
916
|
-
# I recommend the usage of a case statement. e,g:
|
955
|
+
If you need to handle a specific error, I recommend the usage of a case statement. e,g:
|
917
956
|
|
918
|
-
|
919
|
-
|
957
|
+
```ruby
|
958
|
+
result.on_failure(:exception) do |data, use_case|
|
959
|
+
case exception = data[:exception]
|
920
960
|
when ZeroDivisionError then AppLogger.error(exception.message)
|
921
961
|
else AppLogger.debug("#{use_case.class.name} was the use case responsible for the exception")
|
922
962
|
end
|
923
963
|
end
|
924
|
-
|
925
|
-
# Another note:
|
926
|
-
# ------------
|
927
|
-
# It is possible to rescue an exception even when is a safe use case.
|
928
|
-
# Examples: https://github.com/serradura/u-case/blob/5a85fc238b63811a32737493dc6c59965f92491d/test/micro/case/safe_test.rb#L95-L123
|
929
964
|
```
|
930
965
|
|
966
|
+
> **Note:** It is possible to rescue an exception even when is a safe use case. Examples: https://github.com/serradura/u-case/blob/714c6b658fc6aa02617e6833ddee09eddc760f2a/test/micro/case/safe_test.rb#L90-L118
|
967
|
+
|
931
968
|
[⬆️ Back to Top](#table-of-contents-)
|
932
969
|
|
933
|
-
#### `Micro::
|
970
|
+
#### `Micro::Cases::Safe::Flow`
|
934
971
|
|
935
972
|
As the safe use cases, safe flows can intercept an exception in any of its steps. These are the ways to define one:
|
936
973
|
|
937
974
|
```ruby
|
938
975
|
module Users
|
939
|
-
Create =
|
940
|
-
end
|
941
|
-
|
942
|
-
# Note:
|
943
|
-
# The ampersand is based on the safe navigation operator. https://ruby-doc.org/core-2.6/doc/syntax/calling_methods_rdoc.html#label-Safe+navigation+operator
|
944
|
-
|
945
|
-
# The alternatives to declare a safe flow are:
|
946
|
-
|
947
|
-
module Users
|
948
|
-
Create = Micro::Case::Safe::Flow([
|
976
|
+
Create = Micro::Cases.safe_flow([
|
949
977
|
ProcessParams,
|
950
978
|
ValidateParams,
|
951
979
|
Persist,
|
952
980
|
SendToCRM
|
953
981
|
])
|
954
982
|
end
|
983
|
+
```
|
955
984
|
|
956
|
-
|
985
|
+
Defining within classes:
|
957
986
|
|
987
|
+
```ruby
|
958
988
|
module Users
|
959
989
|
class Create < Micro::Case::Safe
|
960
990
|
flow ProcessParams,
|
@@ -963,23 +993,6 @@ module Users
|
|
963
993
|
SendToCRM
|
964
994
|
end
|
965
995
|
end
|
966
|
-
|
967
|
-
# !------------------------------------------ ! #
|
968
|
-
# ! Deprecated: Micro::Case::Safe::Flow mixin ! #
|
969
|
-
# !-------------------------------------------! #
|
970
|
-
|
971
|
-
# The code below still works, but it will output a warning message:
|
972
|
-
# Deprecation: Micro::Case::Flow mixin is being deprecated, please use `Micro::Case` inheritance instead.
|
973
|
-
|
974
|
-
module Users
|
975
|
-
class Create
|
976
|
-
include Micro::Case::Safe::Flow
|
977
|
-
|
978
|
-
flow ProcessParams, ValidateParams, Persist, SendToCRM
|
979
|
-
end
|
980
|
-
end
|
981
|
-
|
982
|
-
# Note: This feature will be removed in the next major release (3.0)
|
983
996
|
```
|
984
997
|
|
985
998
|
[⬆️ Back to Top](#table-of-contents-)
|
@@ -990,16 +1003,16 @@ In functional programming errors/exceptions are handled as regular data, the ide
|
|
990
1003
|
|
991
1004
|
To address this the `Micro::Case::Result` has a special hook `#on_exception` to helping you to handle the control flow in the case of exceptions.
|
992
1005
|
|
993
|
-
> **Note**: this feature will work better if you use it with a `Micro::Case::Safe` use case
|
1006
|
+
> **Note**: this feature will work better if you use it with a `Micro::Case::Safe` flow or use case.
|
994
1007
|
|
995
|
-
How does it work
|
1008
|
+
**How does it work?**
|
996
1009
|
|
997
1010
|
```ruby
|
998
1011
|
class Divide < Micro::Case::Safe
|
999
1012
|
attributes :a, :b
|
1000
1013
|
|
1001
1014
|
def call!
|
1002
|
-
Success
|
1015
|
+
Success result: { division: a / b }
|
1003
1016
|
end
|
1004
1017
|
end
|
1005
1018
|
|
@@ -1028,44 +1041,46 @@ Divide.
|
|
1028
1041
|
# Oh no, something went wrong!
|
1029
1042
|
```
|
1030
1043
|
|
1031
|
-
As you can see, this hook has the same behavior of `result.on_failure(:exception)`, but, the
|
1044
|
+
As you can see, this hook has the same behavior of `result.on_failure(:exception)`, but, the idea here is to have a better communication in the code, making an explicit reference when some failure happened because of an exception.
|
1032
1045
|
|
1033
1046
|
[⬆️ Back to Top](#table-of-contents-)
|
1034
1047
|
|
1035
|
-
### `u-case/with_activemodel_validation` - How to validate use case attributes?
|
1048
|
+
### `u-case/with_activemodel_validation` - How to validate the use case attributes?
|
1036
1049
|
|
1037
1050
|
**Requirement:**
|
1038
1051
|
|
1039
1052
|
To do this your application must have the [activemodel >= 3.2, < 6.1.0](https://rubygems.org/gems/activemodel) as a dependency.
|
1040
1053
|
|
1054
|
+
By default, if your application has ActiveModel as a dependency, any kind of use case can make use of it to validate its attributes.
|
1055
|
+
|
1041
1056
|
```ruby
|
1042
|
-
#
|
1043
|
-
# By default, if your application has the activemodel as a dependency,
|
1044
|
-
# any kind of use case can use it to validate their attributes.
|
1045
|
-
#
|
1046
1057
|
class Multiply < Micro::Case
|
1047
1058
|
attributes :a, :b
|
1048
1059
|
|
1049
1060
|
validates :a, :b, presence: true, numericality: true
|
1050
1061
|
|
1051
1062
|
def call!
|
1052
|
-
return Failure
|
1063
|
+
return Failure :validation_error, result: { errors: self.errors } if invalid?
|
1053
1064
|
|
1054
|
-
Success
|
1065
|
+
Success result: { number: a * b }
|
1055
1066
|
end
|
1056
1067
|
end
|
1068
|
+
```
|
1057
1069
|
|
1058
|
-
|
1059
|
-
# But if do you want an automatic way to fail
|
1060
|
-
# your use cases on validation errors, you can use:
|
1070
|
+
But if do you want an automatic way to fail your use cases on validation errors, you could do:
|
1061
1071
|
|
1062
|
-
|
1063
|
-
require 'u-case/with_activemodel_validation' # or require 'micro/case/with_validation'
|
1072
|
+
1. **require 'u-case/with_activemodel_validation'** in the Gemfile
|
1064
1073
|
|
1065
|
-
|
1066
|
-
gem 'u-case', require: 'u-case/with_activemodel_validation'
|
1074
|
+
```ruby
|
1075
|
+
gem 'u-case', require: 'u-case/with_activemodel_validation'
|
1076
|
+
```
|
1067
1077
|
|
1068
|
-
|
1078
|
+
2. Use the `Micro::Case.config` to enable it. [Link to](#microcaseconfig) this section.
|
1079
|
+
|
1080
|
+
Using this approach, you can rewrite the previous example with less code. e.g:
|
1081
|
+
|
1082
|
+
```ruby
|
1083
|
+
require 'u-case/with_activemodel_validation'
|
1069
1084
|
|
1070
1085
|
class Multiply < Micro::Case
|
1071
1086
|
attributes :a, :b
|
@@ -1073,19 +1088,16 @@ class Multiply < Micro::Case
|
|
1073
1088
|
validates :a, :b, presence: true, numericality: true
|
1074
1089
|
|
1075
1090
|
def call!
|
1076
|
-
Success
|
1091
|
+
Success result: { number: a * b }
|
1077
1092
|
end
|
1078
1093
|
end
|
1079
|
-
|
1080
|
-
# Note:
|
1081
|
-
# ----
|
1082
|
-
# After requiring the validation mode, the
|
1083
|
-
# Micro::Case::Strict and Micro::Case::Safe classes will inherit this new behavior.
|
1084
1094
|
```
|
1085
1095
|
|
1086
|
-
|
1096
|
+
> **Note:** After requiring the validation mode, the `Micro::Case::Strict` and `Micro::Case::Safe` classes will inherit this new behavior.
|
1087
1097
|
|
1088
|
-
|
1098
|
+
#### If I enabled the auto validation, is it possible to disable it only in specific use cases?
|
1099
|
+
|
1100
|
+
Answer: Yes, it is possible. To do this, you will need to use the `disable_auto_validation` macro. e.g:
|
1089
1101
|
|
1090
1102
|
```ruby
|
1091
1103
|
require 'u-case/with_activemodel_validation'
|
@@ -1098,13 +1110,13 @@ class Multiply < Micro::Case
|
|
1098
1110
|
validates :a, :b, presence: true, numericality: true
|
1099
1111
|
|
1100
1112
|
def call!
|
1101
|
-
Success
|
1113
|
+
Success result: { number: a * b }
|
1102
1114
|
end
|
1103
1115
|
end
|
1104
1116
|
|
1105
1117
|
Multiply.call(a: 2, b: 'a')
|
1106
1118
|
|
1107
|
-
# The output will be
|
1119
|
+
# The output will be:
|
1108
1120
|
# TypeError (String can't be coerced into Integer)
|
1109
1121
|
```
|
1110
1122
|
|
@@ -1112,9 +1124,9 @@ Multiply.call(a: 2, b: 'a')
|
|
1112
1124
|
|
1113
1125
|
#### `Kind::Validator`
|
1114
1126
|
|
1115
|
-
The [kind gem](https://github.com/serradura/kind) has a module to enable the validation of data type through [`ActiveModel validations`](https://guides.rubyonrails.org/active_model_basics.html#validations). So, when you require the `'u-case/with_activemodel_validation'`, this module will require the [`Kind::Validator`](https://github.com/serradura/kind#kindvalidator-activemodelvalidations).
|
1127
|
+
The [kind gem](https://github.com/serradura/kind) has a module to enable the validation of data type through [`ActiveModel validations`](https://guides.rubyonrails.org/active_model_basics.html#validations). So, when you require the `'u-case/with_activemodel_validation'`, this module will also require the [`Kind::Validator`](https://github.com/serradura/kind#kindvalidator-activemodelvalidations).
|
1116
1128
|
|
1117
|
-
The example below shows how to validate the attributes
|
1129
|
+
The example below shows how to validate the attributes types.
|
1118
1130
|
|
1119
1131
|
```ruby
|
1120
1132
|
class Todo::List::AddItem < Micro::Case
|
@@ -1124,77 +1136,83 @@ class Todo::List::AddItem < Micro::Case
|
|
1124
1136
|
validates :params, kind: ActionController::Parameters
|
1125
1137
|
|
1126
1138
|
def call!
|
1127
|
-
todo_params =
|
1139
|
+
todo_params = params.require(:todo).permit(:title, :due_at)
|
1128
1140
|
|
1129
1141
|
todo = user.todos.create(todo_params)
|
1130
1142
|
|
1131
|
-
Success
|
1143
|
+
Success result: { todo: todo }
|
1132
1144
|
rescue ActionController::ParameterMissing => e
|
1133
|
-
Failure
|
1145
|
+
Failure :parameter_missing, result: { message: e.message }
|
1134
1146
|
end
|
1135
1147
|
end
|
1136
1148
|
```
|
1137
1149
|
|
1138
|
-
|
1150
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1139
1151
|
|
1140
|
-
|
1152
|
+
## `Micro::Case.config`
|
1141
1153
|
|
1142
|
-
|
1154
|
+
The idea of this resource is to allow the configuration of some `u-case` features/modules.
|
1155
|
+
I recommend you use it only once in your codebase. e.g. In a Rails initializer.
|
1143
1156
|
|
1144
|
-
|
1157
|
+
You can see below, which are the available configurations with their default values:
|
1145
1158
|
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
| Interactor | 21230.5 | 5.49x slower |
|
1151
|
-
| Trailblazer::Operation | 16466.6 | 7.08x slower |
|
1152
|
-
| Dry::Transaction | 5069.5 | 23.00x slower |
|
1159
|
+
```ruby
|
1160
|
+
Micro::Case.config do |config|
|
1161
|
+
# Use ActiveModel to auto-validate your use cases' attributes.
|
1162
|
+
config.enable_activemodel_validation = false
|
1153
1163
|
|
1154
|
-
|
1164
|
+
# Use to enable/disable the `Micro::Case::Results#transitions`.
|
1165
|
+
config.enable_transitions = true
|
1166
|
+
end
|
1167
|
+
```
|
1168
|
+
|
1169
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1170
|
+
|
1171
|
+
## Benchmarks
|
1172
|
+
|
1173
|
+
### `Micro::Case` (v3.0.0)
|
1155
1174
|
|
1156
1175
|
#### Success results
|
1157
1176
|
|
1158
1177
|
| Gem / Abstraction | Iterations per second | Comparison |
|
1159
1178
|
| ----------------- | --------------------: | ----------------: |
|
1160
|
-
| Dry::Monads |
|
1161
|
-
| **Micro::Case** |
|
1162
|
-
| Interactor |
|
1163
|
-
| Trailblazer::Operation |
|
1164
|
-
| Dry::Transaction |
|
1179
|
+
| Dry::Monads | 139037.7 | _**The Fastest**_ |
|
1180
|
+
| **Micro::Case** | 101497.3 | 1.37x slower |
|
1181
|
+
| Interactor | 30694.2 | 4.53x slower |
|
1182
|
+
| Trailblazer::Operation | 14580.8 | 9.54x slower |
|
1183
|
+
| Dry::Transaction | 5728.0 | 24.27x slower |
|
1165
1184
|
|
1166
1185
|
<details>
|
1167
1186
|
<summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
|
1168
1187
|
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
```
|
1188
|
+
```ruby
|
1189
|
+
# Warming up --------------------------------------
|
1190
|
+
# Interactor 3.056k i/100ms
|
1191
|
+
# Trailblazer::Operation 1.480k i/100ms
|
1192
|
+
# Dry::Monads 14.316k i/100ms
|
1193
|
+
# Dry::Transaction 576.000 i/100ms
|
1194
|
+
# Micro::Case 10.388k i/100ms
|
1195
|
+
# Micro::Case::Strict 8.223k i/100ms
|
1196
|
+
# Micro::Case::Safe 10.057k i/100ms
|
1197
|
+
|
1198
|
+
# Calculating -------------------------------------
|
1199
|
+
# Interactor 30.694k (± 2.3%) i/s - 155.856k in 5.080475s
|
1200
|
+
# Trailblazer::Operation 14.581k (± 3.9%) i/s - 74.000k in 5.083091s
|
1201
|
+
# Dry::Monads 139.038k (± 3.0%) i/s - 701.484k in 5.049921s
|
1202
|
+
# Dry::Transaction 5.728k (± 3.6%) i/s - 28.800k in 5.034599s
|
1203
|
+
# Micro::Case 100.712k (± 3.4%) i/s - 509.012k in 5.060139s
|
1204
|
+
# Micro::Case::Strict 81.513k (± 3.4%) i/s - 411.150k in 5.049962s
|
1205
|
+
# Micro::Case::Safe 101.497k (± 3.1%) i/s - 512.907k in 5.058463s
|
1206
|
+
|
1207
|
+
# Comparison:
|
1208
|
+
# Dry::Monads: 139037.7 i/s
|
1209
|
+
# Micro::Case::Safe: 101497.3 i/s - 1.37x (± 0.00) slower
|
1210
|
+
# Micro::Case: 100711.6 i/s - 1.38x (± 0.00) slower
|
1211
|
+
# Micro::Case::Strict: 81512.9 i/s - 1.71x (± 0.00) slower
|
1212
|
+
# Interactor: 30694.2 i/s - 4.53x (± 0.00) slower
|
1213
|
+
# Trailblazer::Operation: 14580.8 i/s - 9.54x (± 0.00) slower
|
1214
|
+
# Dry::Transaction: 5728.0 i/s - 24.27x (± 0.00) slower
|
1215
|
+
```
|
1198
1216
|
</details>
|
1199
1217
|
|
1200
1218
|
https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_success_result.rb
|
@@ -1203,100 +1221,113 @@ https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_success
|
|
1203
1221
|
|
1204
1222
|
| Gem / Abstraction | Iterations per second | Comparison |
|
1205
1223
|
| ----------------- | --------------------: | ----------------: |
|
1206
|
-
| **Micro::Case** |
|
1207
|
-
| Dry::Monads |
|
1208
|
-
| Trailblazer::Operation |
|
1209
|
-
| Interactor |
|
1210
|
-
| Dry::Transaction |
|
1224
|
+
| **Micro::Case** | 94619.6 | _**The Fastest**_ |
|
1225
|
+
| Dry::Monads | 70250.6 | 1.35x slower |
|
1226
|
+
| Trailblazer::Operation | 14786.1 | 6.40x slower |
|
1227
|
+
| Interactor | 13770.0 | 6.87x slower |
|
1228
|
+
| Dry::Transaction | 4994.4 | 18.95x slower |
|
1211
1229
|
|
1212
1230
|
<details>
|
1213
1231
|
<summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
|
1214
1232
|
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
```
|
1233
|
+
```ruby
|
1234
|
+
# Warming up --------------------------------------
|
1235
|
+
# Interactor 1.408k i/100ms
|
1236
|
+
# Trailblazer::Operation 1.492k i/100ms
|
1237
|
+
# Dry::Monads 7.224k i/100ms
|
1238
|
+
# Dry::Transaction 501.000 i/100ms
|
1239
|
+
# Micro::Case 9.664k i/100ms
|
1240
|
+
# Micro::Case::Strict 7.823k i/100ms
|
1241
|
+
# Micro::Case::Safe 9.464k i/100ms
|
1242
|
+
|
1243
|
+
# Calculating -------------------------------------
|
1244
|
+
# Interactor 13.770k (± 4.3%) i/s - 68.992k in 5.020330s
|
1245
|
+
# Trailblazer::Operation 14.786k (± 5.3%) i/s - 74.600k in 5.064700s
|
1246
|
+
# Dry::Monads 70.251k (± 6.7%) i/s - 353.976k in 5.063010s
|
1247
|
+
# Dry::Transaction 4.994k (± 4.0%) i/s - 25.050k in 5.023997s
|
1248
|
+
# Micro::Case 94.620k (± 3.8%) i/s - 473.536k in 5.012483s
|
1249
|
+
# Micro::Case::Strict 76.059k (± 3.0%) i/s - 383.327k in 5.044482s
|
1250
|
+
# Micro::Case::Safe 91.719k (± 5.6%) i/s - 463.736k in 5.072552s
|
1251
|
+
|
1252
|
+
# Comparison:
|
1253
|
+
# Micro::Case: 94619.6 i/s
|
1254
|
+
# Micro::Case::Safe: 91719.4 i/s - same-ish: difference falls within error
|
1255
|
+
# Micro::Case::Strict: 76058.7 i/s - 1.24x (± 0.00) slower
|
1256
|
+
# Dry::Monads: 70250.6 i/s - 1.35x (± 0.00) slower
|
1257
|
+
# Trailblazer::Operation: 14786.1 i/s - 6.40x (± 0.00) slower
|
1258
|
+
# Interactor: 13770.0 i/s - 6.87x (± 0.00) slower
|
1259
|
+
# Dry::Transaction: 4994.4 i/s - 18.95x (± 0.00) slower
|
1260
|
+
```
|
1244
1261
|
</details>
|
1245
1262
|
|
1246
1263
|
https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_failure_result.rb
|
1247
1264
|
|
1248
1265
|
---
|
1249
1266
|
|
1250
|
-
### `Micro::
|
1267
|
+
### `Micro::Cases::Flow` (v3.0.0)
|
1251
1268
|
|
1252
1269
|
| Gems / Abstraction | [Success results](https://github.com/serradura/u-case/blob/master/benchmarks/flow/with_success_result.rb#L40) | [Failure results](https://github.com/serradura/u-case/blob/master/benchmarks/flow/with_failure_result.rb#L40) |
|
1253
|
-
|
|
1254
|
-
| Micro::Case
|
1255
|
-
| Micro::Case
|
1256
|
-
|
|
1270
|
+
| ------------------------------------------- | ----------------: | ----------------: |
|
1271
|
+
| Micro::Case internal flow (private methods) | _**The Fastest**_ | _**The Fastest**_ |
|
1272
|
+
| Micro::Case `then` method | 1.48x slower | 0x slower |
|
1273
|
+
| Micro::Cases.flow | 1.62x slower | 1.16x slower |
|
1274
|
+
| Micro::Cases.safe_flow | 1.64x slower | 1.16x slower |
|
1275
|
+
| Interactor::Organizer | 1.95x slower | 6.17x slower |
|
1257
1276
|
|
1258
|
-
\* The `Dry::Monads`, `Dry::Transaction`, `Trailblazer::Operation` are out of this analysis because all of them doesn't have this kind of feature.
|
1277
|
+
\* The `Dry::Monads`, `Dry::Transaction`, `Trailblazer::Operation` gems are out of this analysis because all of them doesn't have this kind of feature.
|
1259
1278
|
|
1260
1279
|
<details>
|
1261
1280
|
<summary><strong>Success results</strong> - Show the full benchmark/ips results.</summary>
|
1262
1281
|
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1268
|
-
|
1269
|
-
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1282
|
+
```ruby
|
1283
|
+
# Warming up --------------------------------------
|
1284
|
+
# Interactor::Organizer 5.219k i/100ms
|
1285
|
+
# Micro::Cases.flow([]) 6.451k i/100ms
|
1286
|
+
# Micro::Cases::safe_flow([]) 6.421k i/100ms
|
1287
|
+
# Micro::Case flow using `then` method 7.139k i/100ms
|
1288
|
+
# Micro::Case flow using private methods 10.355k i/100ms
|
1289
|
+
|
1290
|
+
# Calculating -------------------------------------
|
1291
|
+
# Interactor::Organizer 52.959k (± 1.7%) i/s - 266.169k in 5.027332s
|
1292
|
+
# Micro::Cases.flow([]) 63.947k (± 1.7%) i/s - 322.550k in 5.045597s
|
1293
|
+
# Micro::Cases::safe_flow([]) 63.047k (± 3.1%) i/s - 321.050k in 5.097228s
|
1294
|
+
# Micro::Case flow using `then` method 69.644k (± 4.0%) i/s - 349.811k in 5.031120s
|
1295
|
+
# Micro::Case flow using private methods 103.297k (± 1.4%) i/s - 517.750k in 5.013254s
|
1296
|
+
|
1297
|
+
# Comparison:
|
1298
|
+
# Micro::Case flow using private methods: 103297.4 i/s
|
1299
|
+
# Micro::Case flow using `then` method: 69644.0 i/s - 1.48x (± 0.00) slower
|
1300
|
+
# Micro::Cases.flow([]): 63946.7 i/s - 1.62x (± 0.00) slower
|
1301
|
+
# Micro::Cases::safe_flow([]): 63047.2 i/s - 1.64x (± 0.00) slower
|
1302
|
+
# Interactor::Organizer: 52958.9 i/s - 1.95x (± 0.00) slower
|
1303
|
+
```
|
1279
1304
|
</details>
|
1280
1305
|
|
1281
1306
|
<details>
|
1282
1307
|
<summary><strong>Failure results</strong> - Show the full benchmark/ips results.</summary>
|
1283
1308
|
|
1284
|
-
|
1285
|
-
|
1286
|
-
|
1287
|
-
|
1288
|
-
|
1289
|
-
|
1290
|
-
|
1291
|
-
|
1292
|
-
|
1293
|
-
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
1297
|
-
|
1298
|
-
|
1299
|
-
|
1309
|
+
```ruby
|
1310
|
+
# Warming up --------------------------------------
|
1311
|
+
# Interactor::Organizer 2.381k i/100ms
|
1312
|
+
# Micro::Cases.flow([]) 12.003k i/100ms
|
1313
|
+
# Micro::Cases::safe_flow([]) 12.771k i/100ms
|
1314
|
+
# Micro::Case flow using `then` method 15.085k i/100ms
|
1315
|
+
# Micro::Case flow using private methods 14.254k i/100ms
|
1316
|
+
|
1317
|
+
# Calculating -------------------------------------
|
1318
|
+
# Interactor::Organizer 23.579k (± 3.2%) i/s - 119.050k in 5.054410s
|
1319
|
+
# Micro::Cases.flow([]) 124.072k (± 3.4%) i/s - 624.156k in 5.036618s
|
1320
|
+
# Micro::Cases::safe_flow([]) 124.894k (± 3.6%) i/s - 625.779k in 5.017494s
|
1321
|
+
# Micro::Case flow using `then` method 145.370k (± 4.8%) i/s - 739.165k in 5.096972s
|
1322
|
+
# Micro::Case flow using private methods 139.753k (± 5.6%) i/s - 698.446k in 5.015207s
|
1323
|
+
|
1324
|
+
# Comparison:
|
1325
|
+
# Micro::Case flow using `then` method: 145369.7 i/s
|
1326
|
+
# Micro::Case flow using private methods: 139753.4 i/s - same-ish: difference falls within error
|
1327
|
+
# Micro::Cases::safe_flow([]): 124893.7 i/s - 1.16x (± 0.00) slower
|
1328
|
+
# Micro::Cases.flow([]): 124071.8 i/s - 1.17x (± 0.00) slower
|
1329
|
+
# Interactor::Organizer: 23578.7 i/s - 6.17x (± 0.00) slower
|
1330
|
+
```
|
1300
1331
|
</details>
|
1301
1332
|
|
1302
1333
|
https://github.com/serradura/u-case/tree/master/benchmarks/flow
|
@@ -1314,7 +1345,7 @@ Check it out implementations of the same use case with different gems/abstractio
|
|
1314
1345
|
|
1315
1346
|
### 1️⃣ Rails App (API)
|
1316
1347
|
|
1317
|
-
> This project shows different kinds of architecture (one per commit), and in the last one, how to use the Micro::Case gem to handle the application business logic.
|
1348
|
+
> This project shows different kinds of architecture (one per commit), and in the last one, how to use the `Micro::Case` gem to handle the application business logic.
|
1318
1349
|
>
|
1319
1350
|
> Link: https://github.com/serradura/from-fat-controllers-to-use-cases
|
1320
1351
|
|
@@ -1326,11 +1357,11 @@ Check it out implementations of the same use case with different gems/abstractio
|
|
1326
1357
|
|
1327
1358
|
### 3️⃣ Users creation
|
1328
1359
|
|
1329
|
-
> An example of a use case flow that
|
1360
|
+
> An example of a use case flow that defines steps to sanitize, validate, and persist its input data.
|
1330
1361
|
>
|
1331
1362
|
> Link: https://github.com/serradura/u-case/blob/master/examples/users_creation.rb
|
1332
1363
|
|
1333
|
-
### 4️⃣ Rescuing
|
1364
|
+
### 4️⃣ Rescuing exceptions inside of the use cases
|
1334
1365
|
|
1335
1366
|
> Link: https://github.com/serradura/u-case/blob/master/examples/rescuing_exceptions.rb
|
1336
1367
|
|