u-case 2.6.0 → 3.0.0.rc5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|
[](https://codeclimate.com/github/serradura/u-case/maintainability)
|
5
5
|
[](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
|
|