u-case 2.3.1 → 3.0.0.rc2
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 +21 -1
- data/README.md +588 -331
- data/lib/micro/case.rb +72 -57
- data/lib/micro/case/error.rb +20 -12
- data/lib/micro/case/result.rb +102 -18
- data/lib/micro/case/safe.rb +6 -2
- data/lib/micro/case/utils.rb +19 -0
- data/lib/micro/case/version.rb +1 -1
- data/lib/micro/case/{with_validation.rb → with_activemodel_validation.rb} +2 -2
- data/lib/micro/cases.rb +16 -0
- data/lib/micro/cases/flow.rb +96 -0
- data/lib/micro/cases/safe/flow.rb +18 -0
- data/lib/u-case/with_activemodel_validation.rb +5 -0
- data/u-case.gemspec +2 -1
- metadata +32 -11
- data/lib/micro/case/flow.rb +0 -48
- data/lib/micro/case/flow/reducer.rb +0 -75
- data/lib/micro/case/safe/flow.rb +0 -44
- data/lib/u-case/with_validation.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dfc46204c461937d19681ae26dca7846a8d36720b735d759023b4c1369ef2a9a
|
4
|
+
data.tar.gz: 21bfd7bc9d2c2a13c901672eac77f15da4b7975aeeea925b3f94ff7e36dd1161
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1e6992400490ef920d9ad855ebf698174ee2fcf9ab555db880ab2dea065360b921af7e38e4be5eb74e28c484862a25ca476beba9e2daebbc668bae07ef1b1853
|
7
|
+
data.tar.gz: d081ddf7bf5b3209f31c385060fd8dbbc9f72a8b1a05e553b1666df2f2de1550859017e9aa85aa329118bdef965f390a1637348c2a7549139e462e3cb091c250
|
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
@@ -18,7 +18,27 @@ end
|
|
18
18
|
group :test do
|
19
19
|
gem 'minitest', activemodel_version < '4.1' ? '~> 4.2' : '~> 5.0'
|
20
20
|
gem 'simplecov', require: false
|
21
|
-
|
21
|
+
end
|
22
|
+
|
23
|
+
pry_byebug_version =
|
24
|
+
case RUBY_VERSION
|
25
|
+
when /\A2.2/ then '3.6'
|
26
|
+
when /\A2.3/ then '3.7'
|
27
|
+
else '3.9'
|
28
|
+
end
|
29
|
+
|
30
|
+
pry_version =
|
31
|
+
case RUBY_VERSION
|
32
|
+
when /\A2.2/ then '0.12.2'
|
33
|
+
when /\A2.3/ then '0.12.2'
|
34
|
+
else '0.13.1'
|
35
|
+
end
|
36
|
+
|
37
|
+
group :development, :test do
|
38
|
+
gem 'awesome_print', '~> 1.8'
|
39
|
+
|
40
|
+
gem 'pry', "~> #{pry_version}"
|
41
|
+
gem 'pry-byebug', "~> #{pry_byebug_version}"
|
22
42
|
end
|
23
43
|
|
24
44
|
# Specify your gem's dependencies in u-case.gemspec
|
data/README.md
CHANGED
@@ -1,15 +1,16 @@
|
|
1
|
+

|
1
2
|
[](https://rubygems.org/gems/u-case)
|
2
3
|
[](https://travis-ci.com/serradura/u-case)
|
3
4
|
[](https://codeclimate.com/github/serradura/u-case/maintainability)
|
4
5
|
[](https://codeclimate.com/github/serradura/u-case/test_coverage)
|
5
6
|
|
6
|
-
μ-case (Micro::Case)
|
7
|
-
|
7
|
+
μ-case (Micro::Case) <!-- omit in toc -->
|
8
|
+
====================
|
8
9
|
|
9
10
|
Create simple and powerful use cases as objects.
|
10
11
|
|
11
12
|
The main project goals are:
|
12
|
-
1.
|
13
|
+
1. Easy to use and easy to learn (input **>>** process **>>** output).
|
13
14
|
2. Promote referential transparency (transforming instead of modifying) and data integrity.
|
14
15
|
3. No callbacks (e.g: before, after, around).
|
15
16
|
4. Solve complex business logic, by allowing the composition of use cases.
|
@@ -17,45 +18,59 @@ The main project goals are:
|
|
17
18
|
|
18
19
|
> 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
|
|
21
|
+
## Documentation <!-- omit in toc -->
|
22
|
+
|
23
|
+
Version | Documentation
|
24
|
+
--------- | -------------
|
25
|
+
3.0.0.rc2 | https://github.com/serradura/u-case/blob/master/README.md
|
26
|
+
2.6.0 | https://github.com/serradura/u-case/blob/v2.x/README.md
|
27
|
+
1.1.0 | https://github.com/serradura/u-case/blob/v1.x/README.md
|
28
|
+
|
20
29
|
## Table of Contents <!-- omit in toc -->
|
21
|
-
- [
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
- [
|
26
|
-
|
27
|
-
- [
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
- [
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
- [
|
40
|
-
|
41
|
-
- [
|
42
|
-
|
43
|
-
- [
|
44
|
-
- [Micro::
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
- [
|
49
|
-
|
50
|
-
- [
|
51
|
-
- [
|
52
|
-
- [
|
53
|
-
- [
|
54
|
-
|
55
|
-
- [
|
56
|
-
|
57
|
-
- [
|
58
|
-
- [
|
30
|
+
- [Required Ruby version](#required-ruby-version)
|
31
|
+
- [Dependencies](#dependencies)
|
32
|
+
- [Installation](#installation)
|
33
|
+
- [Usage](#usage)
|
34
|
+
- [`Micro::Case` - How to define a use case?](#microcase---how-to-define-a-use-case)
|
35
|
+
- [`Micro::Case::Result` - What is a use case result?](#microcaseresult---what-is-a-use-case-result)
|
36
|
+
- [What are the default result types?](#what-are-the-default-result-types)
|
37
|
+
- [How to define custom result types?](#how-to-define-custom-result-types)
|
38
|
+
- [Is it possible to define a custom result type without a block?](#is-it-possible-to-define-a-custom-result-type-without-a-block)
|
39
|
+
- [How to use the result hooks?](#how-to-use-the-result-hooks)
|
40
|
+
- [Why the failure hook (without a type) exposes result itself?](#why-the-failure-hook-without-a-type-exposes-result-itself)
|
41
|
+
- [What happens if a result hook was declared multiple times?](#what-happens-if-a-result-hook-was-declared-multiple-times)
|
42
|
+
- [How to use the `Micro::Case::Result#then` method?](#how-to-use-the-microcaseresultthen-method)
|
43
|
+
- [What does happens when a `Micro::Case::Result#then` receives a block?](#what-does-happens-when-a-microcaseresultthen-receives-a-block)
|
44
|
+
- [How to make attributes data injection using this feature?](#how-to-make-attributes-data-injection-using-this-feature)
|
45
|
+
- [`Micro::Cases::Flow` - How to compose use cases?](#microcasesflow---how-to-compose-use-cases)
|
46
|
+
- [Is it possible to compose a use case flow with other ones?](#is-it-possible-to-compose-a-use-case-flow-with-other-ones)
|
47
|
+
- [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)
|
48
|
+
- [How to understand what is happening during a flow execution?](#how-to-understand-what-is-happening-during-a-flow-execution)
|
49
|
+
- [`Micro::Case::Result#transitions` schema](#microcaseresulttransitions-schema)
|
50
|
+
- [Is it possible to declare a flow which includes the use case itself?](#is-it-possible-to-declare-a-flow-which-includes-the-use-case-itself)
|
51
|
+
- [`Micro::Case::Strict` - What is a strict use case?](#microcasestrict---what-is-a-strict-use-case)
|
52
|
+
- [`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)
|
53
|
+
- [`Micro::Cases::Safe::Flow`](#microcasessafeflow)
|
54
|
+
- [`Micro::Case::Result#on_exception`](#microcaseresulton_exception)
|
55
|
+
- [`u-case/with_activemodel_validation` - How to validate use case attributes?](#u-casewith_activemodel_validation---how-to-validate-use-case-attributes)
|
56
|
+
- [If I enabled the auto validation, is it possible to disable it only in specific use case classes?](#if-i-enabled-the-auto-validation-is-it-possible-to-disable-it-only-in-specific-use-case-classes)
|
57
|
+
- [`Kind::Validator`](#kindvalidator)
|
58
|
+
- [Benchmarks](#benchmarks)
|
59
|
+
- [`Micro::Case` (v2.6.0)](#microcase-v260)
|
60
|
+
- [Best overall](#best-overall)
|
61
|
+
- [Success results](#success-results)
|
62
|
+
- [Failure results](#failure-results)
|
63
|
+
- [`Micro::Case::Flow` (v2.6.0)](#microcaseflow-v260)
|
64
|
+
- [Comparisons](#comparisons)
|
65
|
+
- [Examples](#examples)
|
66
|
+
- [1️⃣ Rails App (API)](#1️⃣-rails-app-api)
|
67
|
+
- [2️⃣ CLI calculator](#2️⃣-cli-calculator)
|
68
|
+
- [3️⃣ Users creation](#3️⃣-users-creation)
|
69
|
+
- [4️⃣ Rescuing exception inside of the use cases](#4️⃣-rescuing-exception-inside-of-the-use-cases)
|
70
|
+
- [Development](#development)
|
71
|
+
- [Contributing](#contributing)
|
72
|
+
- [License](#license)
|
73
|
+
- [Code of Conduct](#code-of-conduct)
|
59
74
|
|
60
75
|
## Required Ruby version
|
61
76
|
|
@@ -63,8 +78,15 @@ The main project goals are:
|
|
63
78
|
|
64
79
|
## Dependencies
|
65
80
|
|
66
|
-
|
67
|
-
|
81
|
+
1. [`kind`](https://github.com/serradura/kind) gem.
|
82
|
+
|
83
|
+
A simple type system (at runtime) for Ruby.
|
84
|
+
|
85
|
+
Used to validate method inputs using its [`activemodel validation`](https://github.com/serradura/kind#kindvalidator-activemodelvalidations) module is auto required by [`u-case/with_activemodel_validation`](#u-casewith_activemodel_validation---how-to-validate-use-case-attributes) mode, and expose `Kind::Of::Micro::Case`, `Kind::Of::Micro::Case::Result` type checkers.
|
86
|
+
2. [`u-attributes`](https://github.com/serradura/u-attributes) gem.
|
87
|
+
|
88
|
+
This gem allows defining read-only attributes, that is, your objects will have only getters to access their attributes data.
|
89
|
+
It is used to define the use case attributes.
|
68
90
|
|
69
91
|
## Installation
|
70
92
|
|
@@ -94,11 +116,11 @@ class Multiply < Micro::Case
|
|
94
116
|
# 2. Define the method `call!` with its business logic
|
95
117
|
def call!
|
96
118
|
|
97
|
-
# 3. Wrap the use case result/output using the `Success()` or `Failure()` methods
|
119
|
+
# 3. Wrap the use case result/output using the `Success(result: *)` or `Failure(result: *)` methods
|
98
120
|
if a.is_a?(Numeric) && b.is_a?(Numeric)
|
99
|
-
Success
|
121
|
+
Success result: { number: a * b }
|
100
122
|
else
|
101
|
-
Failure { '`a` and `b` attributes must be numeric' }
|
123
|
+
Failure result: { message: '`a` and `b` attributes must be numeric' }
|
102
124
|
end
|
103
125
|
end
|
104
126
|
end
|
@@ -112,14 +134,14 @@ end
|
|
112
134
|
result = Multiply.call(a: 2, b: 2)
|
113
135
|
|
114
136
|
result.success? # true
|
115
|
-
result.
|
137
|
+
result.data # { number: 4 }
|
116
138
|
|
117
139
|
# Failure result
|
118
140
|
|
119
141
|
bad_result = Multiply.call(a: 2, b: '2')
|
120
142
|
|
121
143
|
bad_result.failure? # true
|
122
|
-
bad_result.
|
144
|
+
bad_result.data # { message: "`a` and `b` attributes must be numeric" }
|
123
145
|
|
124
146
|
#-----------------------------#
|
125
147
|
# Calling a use case instance #
|
@@ -127,7 +149,7 @@ bad_result.value # "`a` and `b` attributes must be numeric"
|
|
127
149
|
|
128
150
|
result = Multiply.new(a: 2, b: 3).call
|
129
151
|
|
130
|
-
result.value # 6
|
152
|
+
result.value # { number: 6 }
|
131
153
|
|
132
154
|
# Note:
|
133
155
|
# ----
|
@@ -142,11 +164,14 @@ result.value # 6
|
|
142
164
|
A `Micro::Case::Result` stores the use cases output data. These are their main methods:
|
143
165
|
- `#success?` returns true if is a successful result.
|
144
166
|
- `#failure?` returns true if is an unsuccessful result.
|
145
|
-
- `#
|
167
|
+
- `#data` the result data itself.
|
146
168
|
- `#type` a Symbol which gives meaning for the result, this is useful to declare different types of failures or success.
|
147
|
-
- `#on_success` or `#on_failure` are hook methods that help you define the application flow.
|
169
|
+
- `#on_success` or `#on_failure` are hook methods that help you to define the application flow.
|
148
170
|
- `#use_case` if is a failure result, the use case responsible for it will be accessible through this method. This feature is handy to handle a flow failure (this topic will be covered ahead).
|
149
|
-
- `#then`
|
171
|
+
- `#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.
|
172
|
+
- `#[]` and `#values_at` are shortcuts to access the `#data` values.
|
173
|
+
|
174
|
+
> **Note:** for backward compatibility, you could use the `#value` method as an alias of `#data` method.
|
150
175
|
|
151
176
|
[⬆️ Back to Top](#table-of-contents-)
|
152
177
|
|
@@ -161,9 +186,13 @@ class Divide < Micro::Case
|
|
161
186
|
attributes :a, :b
|
162
187
|
|
163
188
|
def call!
|
164
|
-
invalid_attributes.empty?
|
165
|
-
|
166
|
-
|
189
|
+
if invalid_attributes.empty?
|
190
|
+
Success result: { number: a / b }
|
191
|
+
else
|
192
|
+
Failure result: { invalid_attributes: invalid_attributes }
|
193
|
+
end
|
194
|
+
rescue => exception
|
195
|
+
Failure result: exception
|
167
196
|
end
|
168
197
|
|
169
198
|
private def invalid_attributes
|
@@ -176,7 +205,7 @@ end
|
|
176
205
|
result = Divide.call(a: 2, b: 2)
|
177
206
|
|
178
207
|
result.type # :ok
|
179
|
-
result.
|
208
|
+
result.data # { number: 1 }
|
180
209
|
result.success? # true
|
181
210
|
result.use_case # raises `Micro::Case::Error::InvalidAccessToTheUseCaseObject: only a failure result can access its own use case`
|
182
211
|
|
@@ -185,40 +214,42 @@ result.use_case # raises `Micro::Case::Error::InvalidAccessToTheUseCaseObject: o
|
|
185
214
|
bad_result = Divide.call(a: 2, b: '2')
|
186
215
|
|
187
216
|
bad_result.type # :error
|
188
|
-
bad_result.
|
217
|
+
bad_result.data # { invalid_attributes: { "b"=>"2" } }
|
189
218
|
bad_result.failure? # true
|
190
|
-
bad_result.use_case # #<Divide:0x0000 @__attributes={"a"=>2, "b"=>"2"}, @a=2, @b="2", @__result=#<Micro::Case::Result:0x0000 @use_case=#<Divide:0x0000 ...>, @type=:error, @value={"b"=>"2"}, @success=false
|
219
|
+
bad_result.use_case # #<Divide:0x0000 @__attributes={"a"=>2, "b"=>"2"}, @a=2, @b="2", @__result=#<Micro::Case::Result:0x0000 @use_case=#<Divide:0x0000 ...>, @type=:error, @value={"b"=>"2"}, @success=false>
|
191
220
|
|
192
221
|
# Failure result (type == :exception)
|
193
222
|
|
194
223
|
err_result = Divide.call(a: 2, b: 0)
|
195
224
|
|
196
225
|
err_result.type # :exception
|
197
|
-
err_result.
|
226
|
+
err_result.data # { exception: <ZeroDivisionError: divided by 0> }
|
198
227
|
err_result.failure? # true
|
199
|
-
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
|
228
|
+
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>
|
200
229
|
|
201
230
|
# Note:
|
202
231
|
# ----
|
203
232
|
# Any Exception instance which is wrapped by
|
204
|
-
# the Failure() method will receive `:exception` instead of the `:error` type.
|
233
|
+
# the Failure(result: *) method will receive `:exception` instead of the `:error` type.
|
205
234
|
```
|
206
235
|
|
207
236
|
[⬆️ Back to Top](#table-of-contents-)
|
208
237
|
|
209
238
|
#### How to define custom result types?
|
210
239
|
|
211
|
-
Answer: Use a symbol as the argument of `Success()`, `Failure()` methods and declare
|
240
|
+
Answer: Use a symbol as the argument of `Success()`, `Failure()` methods and declare the `result:` keyword to set the result data.
|
212
241
|
|
213
242
|
```ruby
|
214
243
|
class Multiply < Micro::Case
|
215
244
|
attributes :a, :b
|
216
245
|
|
217
246
|
def call!
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
247
|
+
if a.is_a?(Numeric) && b.is_a?(Numeric)
|
248
|
+
Success result: { number: a * b }
|
249
|
+
else
|
250
|
+
Failure :invalid_data, result: {
|
251
|
+
attributes: attributes.reject { |_, input| input.is_a?(Numeric) }
|
252
|
+
}
|
222
253
|
end
|
223
254
|
end
|
224
255
|
end
|
@@ -228,7 +259,7 @@ end
|
|
228
259
|
result = Multiply.call(a: 3, b: 2)
|
229
260
|
|
230
261
|
result.type # :ok
|
231
|
-
result.
|
262
|
+
result.data # { number: 6 }
|
232
263
|
result.success? # true
|
233
264
|
|
234
265
|
# Failure result
|
@@ -236,7 +267,7 @@ result.success? # true
|
|
236
267
|
bad_result = Multiply.call(a: 3, b: '2')
|
237
268
|
|
238
269
|
bad_result.type # :invalid_data
|
239
|
-
bad_result.
|
270
|
+
bad_result.data # { attributes: {"b"=>"2"} }
|
240
271
|
bad_result.failure? # true
|
241
272
|
```
|
242
273
|
|
@@ -244,23 +275,25 @@ bad_result.failure? # true
|
|
244
275
|
|
245
276
|
#### Is it possible to define a custom result type without a block?
|
246
277
|
|
247
|
-
Answer: Yes, it is. But
|
278
|
+
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.
|
248
279
|
|
249
280
|
```ruby
|
250
281
|
class Multiply < Micro::Case
|
251
282
|
attributes :a, :b
|
252
283
|
|
253
284
|
def call!
|
254
|
-
|
255
|
-
|
256
|
-
|
285
|
+
if a.is_a?(Numeric) && b.is_a?(Numeric)
|
286
|
+
Success result: { number: a * b }
|
287
|
+
else
|
288
|
+
Failure(:invalid_data)
|
289
|
+
end
|
257
290
|
end
|
258
291
|
end
|
259
292
|
|
260
293
|
result = Multiply.call(a: 2, b: '2')
|
261
294
|
|
262
295
|
result.failure? # true
|
263
|
-
result.
|
296
|
+
result.data # { :invalid_data => true }
|
264
297
|
result.type # :invalid_data
|
265
298
|
result.use_case.attributes # {"a"=>2, "b"=>"2"}
|
266
299
|
|
@@ -283,10 +316,10 @@ class Double < Micro::Case
|
|
283
316
|
attribute :number
|
284
317
|
|
285
318
|
def call!
|
286
|
-
return Failure
|
287
|
-
return Failure
|
319
|
+
return Failure :invalid, result: { msg: 'number must be a numeric value' } unless number.is_a?(Numeric)
|
320
|
+
return Failure :lte_zero, result: { msg: 'number must be greater than 0' } if number <= 0
|
288
321
|
|
289
|
-
Success
|
322
|
+
Success result: { number: number * 2 }
|
290
323
|
end
|
291
324
|
end
|
292
325
|
|
@@ -296,9 +329,9 @@ end
|
|
296
329
|
|
297
330
|
Double
|
298
331
|
.call(number: 3)
|
299
|
-
.on_success { |
|
300
|
-
.on_failure(:invalid) { |
|
301
|
-
.on_failure(:lte_zero) { |
|
332
|
+
.on_success { |result| p result[:number] }
|
333
|
+
.on_failure(:invalid) { |result| raise TypeError, result[:msg] }
|
334
|
+
.on_failure(:lte_zero) { |result| raise ArgumentError, result[:msg] }
|
302
335
|
|
303
336
|
# The output because it is a success:
|
304
337
|
# 6
|
@@ -309,10 +342,10 @@ Double
|
|
309
342
|
|
310
343
|
Double
|
311
344
|
.call(number: -1)
|
312
|
-
.on_success { |
|
345
|
+
.on_success { |result| p result[:number] }
|
313
346
|
.on_failure { |_result, use_case| puts "#{use_case.class.name} was the use case responsible for the failure" }
|
314
|
-
.on_failure(:invalid) { |
|
315
|
-
.on_failure(:lte_zero) { |
|
347
|
+
.on_failure(:invalid) { |result| raise TypeError, result[:msg] }
|
348
|
+
.on_failure(:lte_zero) { |result| raise ArgumentError, result[:msg] }
|
316
349
|
|
317
350
|
# The outputs will be:
|
318
351
|
#
|
@@ -324,7 +357,7 @@ Double
|
|
324
357
|
# The use case responsible for the failure will be accessible as the second hook argument
|
325
358
|
```
|
326
359
|
|
327
|
-
#### Why the failure hook (without a type) exposes
|
360
|
+
#### Why the failure hook (without a type) exposes result itself?
|
328
361
|
|
329
362
|
Answer: To allow you to define how to handle the program flow using some
|
330
363
|
conditional statement (like an `if`, `case/when`).
|
@@ -335,9 +368,9 @@ class Double < Micro::Case
|
|
335
368
|
|
336
369
|
def call!
|
337
370
|
return Failure(:invalid) unless number.is_a?(Numeric)
|
338
|
-
return Failure
|
371
|
+
return Failure :lte_zero, result: attributes(:number) if number <= 0
|
339
372
|
|
340
|
-
Success
|
373
|
+
Success result: { number: number * 2 }
|
341
374
|
end
|
342
375
|
end
|
343
376
|
|
@@ -349,32 +382,32 @@ Double
|
|
349
382
|
.call(-1)
|
350
383
|
.on_failure do |result, use_case|
|
351
384
|
case result.type
|
352
|
-
when :invalid then raise TypeError,
|
353
|
-
when :lte_zero then raise ArgumentError, "
|
385
|
+
when :invalid then raise TypeError, "number must be a numeric value"
|
386
|
+
when :lte_zero then raise ArgumentError, "number `#{result[:number]}` must be greater than 0"
|
354
387
|
else raise NotImplementedError
|
355
388
|
end
|
356
389
|
end
|
357
390
|
|
358
391
|
# The output will be the exception:
|
359
392
|
#
|
360
|
-
# ArgumentError (
|
393
|
+
# ArgumentError (number `-1` must be greater than 0)
|
361
394
|
|
362
|
-
|
363
|
-
# Using decomposition to access result
|
364
|
-
|
395
|
+
#=========================================================#
|
396
|
+
# Using decomposition to access the result data and type #
|
397
|
+
#=========================================================#
|
365
398
|
|
366
399
|
# The syntax to decompose an Array can be used in methods, blocks and assigments.
|
367
|
-
# If you doesn't know
|
400
|
+
# If you doesn't know it, check out the Ruby doc:
|
368
401
|
# https://ruby-doc.org/core-2.2.0/doc/syntax/assignment_rdoc.html#label-Array+Decomposition
|
369
402
|
#
|
370
|
-
#
|
403
|
+
# The object exposed in the hook failure is a Micro::Case::Result, and it can be decomposed using this syntax. e.g:
|
371
404
|
|
372
405
|
Double
|
373
406
|
.call(-2)
|
374
|
-
.on_failure do |(
|
407
|
+
.on_failure do |(data, type), use_case|
|
375
408
|
case type
|
376
|
-
when :invalid then raise TypeError, '
|
377
|
-
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"
|
378
411
|
else raise NotImplementedError
|
379
412
|
end
|
380
413
|
end
|
@@ -395,38 +428,43 @@ class Double < Micro::Case
|
|
395
428
|
attributes :number
|
396
429
|
|
397
430
|
def call!
|
398
|
-
|
399
|
-
|
400
|
-
|
431
|
+
if number.is_a?(Numeric)
|
432
|
+
Success :computed, result: { number: number * 2 }
|
433
|
+
else
|
434
|
+
Failure :invalid, result: { msg: 'number must be a numeric value' }
|
435
|
+
end
|
401
436
|
end
|
402
437
|
end
|
403
438
|
|
404
439
|
result = Double.call(number: 3)
|
405
|
-
result.
|
406
|
-
result
|
440
|
+
result.data # { number: 6 }
|
441
|
+
result[:number] * 4 # 24
|
407
442
|
|
408
443
|
accum = 0
|
409
444
|
|
410
|
-
result.on_success { |
|
411
|
-
.on_success { |
|
412
|
-
.on_success(:computed) { |
|
413
|
-
.on_success(:computed) { |
|
445
|
+
result.on_success { |result| accum += result[:number] }
|
446
|
+
.on_success { |result| accum += result[:number] }
|
447
|
+
.on_success(:computed) { |result| accum += result[:number] }
|
448
|
+
.on_success(:computed) { |result| accum += result[:number] }
|
414
449
|
|
415
450
|
accum # 24
|
416
451
|
|
417
|
-
result
|
452
|
+
result[:number] * 4 == accum # true
|
418
453
|
```
|
419
454
|
|
420
455
|
#### How to use the `Micro::Case::Result#then` method?
|
421
456
|
|
457
|
+
This method allows you to create dynamic flows, so, with it,
|
458
|
+
you can add new use cases or flows to continue the result transformation. e.g:
|
459
|
+
|
422
460
|
```ruby
|
423
461
|
class ForbidNegativeNumber < Micro::Case
|
424
462
|
attribute :number
|
425
463
|
|
426
464
|
def call!
|
427
|
-
return Success
|
465
|
+
return Success result: attributes if number >= 0
|
428
466
|
|
429
|
-
Failure
|
467
|
+
Failure result: attributes
|
430
468
|
end
|
431
469
|
end
|
432
470
|
|
@@ -434,7 +472,7 @@ class Add3 < Micro::Case
|
|
434
472
|
attribute :number
|
435
473
|
|
436
474
|
def call!
|
437
|
-
Success
|
475
|
+
Success result: { number: number + 3 }
|
438
476
|
end
|
439
477
|
end
|
440
478
|
|
@@ -443,8 +481,7 @@ result1 =
|
|
443
481
|
.call(number: -1)
|
444
482
|
.then(Add3)
|
445
483
|
|
446
|
-
result1.
|
447
|
-
result1.value # {'number' => -1}
|
484
|
+
result1.data # {'number' => -1}
|
448
485
|
result1.failure? # true
|
449
486
|
|
450
487
|
# ---
|
@@ -454,16 +491,69 @@ result2 =
|
|
454
491
|
.call(number: 1)
|
455
492
|
.then(Add3)
|
456
493
|
|
457
|
-
result2.
|
458
|
-
result2.value # {'number' => 4}
|
494
|
+
result2.data # {'number' => 4}
|
459
495
|
result2.success? # true
|
460
496
|
```
|
461
497
|
|
498
|
+
> **Note:** this method changes the [`Micro::Case::Result#transitions`](#how-to-understand-what-is-happening-during-a-flow-execution).
|
499
|
+
|
500
|
+
[⬆️ Back to Top](#table-of-contents-)
|
501
|
+
|
502
|
+
##### What does happens when a `Micro::Case::Result#then` receives a block?
|
503
|
+
|
504
|
+
It will yields self (a `Micro::Case::Result instance`) to the block and return the result of the block. e.g:
|
505
|
+
|
506
|
+
```ruby
|
507
|
+
class Add < Micro::Case
|
508
|
+
attributes :a, :b
|
509
|
+
|
510
|
+
def call!
|
511
|
+
if Kind.of?(Numeric, a, b)
|
512
|
+
Success result: { sum: a + b }
|
513
|
+
else
|
514
|
+
Failure(:attributes_arent_numbers)
|
515
|
+
end
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
# --
|
520
|
+
|
521
|
+
success_result =
|
522
|
+
Add
|
523
|
+
.call(a: 2, b: 2)
|
524
|
+
.then { |result| result.success? ? result[:sum] : 0 }
|
525
|
+
|
526
|
+
puts success_result # 4
|
527
|
+
|
528
|
+
# --
|
529
|
+
|
530
|
+
failure_result =
|
531
|
+
Add
|
532
|
+
.call(a: 2, b: '2')
|
533
|
+
.then { |result| result.success? ? result[:sum] : 0 }
|
534
|
+
|
535
|
+
puts failure_result # 0
|
536
|
+
```
|
537
|
+
|
538
|
+
[⬆️ Back to Top](#table-of-contents-)
|
539
|
+
|
540
|
+
##### How to make attributes data injection using this feature?
|
541
|
+
|
542
|
+
Pass a Hash as the second argument of the `Micro::Case::Result#then` method.
|
543
|
+
|
544
|
+
```ruby
|
545
|
+
Todo::FindAllForUser
|
546
|
+
.call(user: current_user, params: params)
|
547
|
+
.then(Paginate)
|
548
|
+
.then(Serialize::PaginatedRelationAsJson, serializer: Todo::Serializer)
|
549
|
+
.on_success { |result| render_json(200, data: result[:todos]) }
|
550
|
+
```
|
551
|
+
|
462
552
|
[⬆️ Back to Top](#table-of-contents-)
|
463
553
|
|
464
|
-
### `Micro::
|
554
|
+
### `Micro::Cases::Flow` - How to compose use cases?
|
465
555
|
|
466
|
-
In this case, this will be a **flow** (`Micro::
|
556
|
+
In this case, this will be a **flow** (`Micro::Cases::Flow`).
|
467
557
|
The main idea of this feature is to use/reuse use cases as steps of a new use case.
|
468
558
|
|
469
559
|
```ruby
|
@@ -473,9 +563,9 @@ module Steps
|
|
473
563
|
|
474
564
|
def call!
|
475
565
|
if numbers.all? { |value| String(value) =~ /\d+/ }
|
476
|
-
Success
|
566
|
+
Success result: { numbers: numbers.map(&:to_i) }
|
477
567
|
else
|
478
|
-
Failure
|
568
|
+
Failure result: { message: 'numbers must contain only numeric types' }
|
479
569
|
end
|
480
570
|
end
|
481
571
|
end
|
@@ -484,7 +574,7 @@ module Steps
|
|
484
574
|
attribute :numbers
|
485
575
|
|
486
576
|
def call!
|
487
|
-
Success
|
577
|
+
Success result: { numbers: numbers.map { |number| number + 2 } }
|
488
578
|
end
|
489
579
|
end
|
490
580
|
|
@@ -492,7 +582,7 @@ module Steps
|
|
492
582
|
attribute :numbers
|
493
583
|
|
494
584
|
def call!
|
495
|
-
Success
|
585
|
+
Success result: { numbers: numbers.map { |number| number * 2 } }
|
496
586
|
end
|
497
587
|
end
|
498
588
|
|
@@ -500,24 +590,24 @@ module Steps
|
|
500
590
|
attribute :numbers
|
501
591
|
|
502
592
|
def call!
|
503
|
-
Success
|
593
|
+
Success result: { numbers: numbers.map { |number| number * number } }
|
504
594
|
end
|
505
595
|
end
|
506
596
|
end
|
507
597
|
|
508
|
-
|
509
|
-
# Creating a flow using
|
510
|
-
|
598
|
+
#-------------------------------------------#
|
599
|
+
# Creating a flow using Micro::Cases.flow() #
|
600
|
+
#-------------------------------------------#
|
511
601
|
|
512
|
-
Add2ToAllNumbers = Micro::
|
602
|
+
Add2ToAllNumbers = Micro::Cases.flow([
|
513
603
|
Steps::ConvertTextToNumbers,
|
514
604
|
Steps::Add2
|
515
605
|
])
|
516
606
|
|
517
607
|
result = Add2ToAllNumbers.call(numbers: %w[1 1 2 2 3 4])
|
518
608
|
|
519
|
-
|
520
|
-
|
609
|
+
result.success? # true
|
610
|
+
result.data # {:numbers => [3, 3, 4, 4, 5, 6]}
|
521
611
|
|
522
612
|
#---------------------------------------------------#
|
523
613
|
# An alternative way to create a flow using classes #
|
@@ -532,41 +622,14 @@ DoubleAllNumbers
|
|
532
622
|
.call(numbers: %w[1 1 b 2 3 4])
|
533
623
|
.on_failure { |message| p message } # "numbers must contain only numeric types"
|
534
624
|
|
535
|
-
# !------------------------------------ ! #
|
536
|
-
# ! Deprecated: Micro::Case::Flow mixin ! #
|
537
|
-
# !-------------------------------------! #
|
538
|
-
|
539
|
-
# The code below still works, but it will output a warning message:
|
540
|
-
# Deprecation: Micro::Case::Flow mixin is being deprecated, please use `Micro::Case` inheritance instead.
|
541
|
-
|
542
|
-
class DoubleAllNumbers
|
543
|
-
include Micro::Case::Flow
|
544
|
-
|
545
|
-
flow Steps::ConvertTextToNumbers,
|
546
|
-
Steps::Double
|
547
|
-
end
|
548
|
-
|
549
|
-
# Note: This feature will be removed in the next major release (3.0)
|
550
|
-
|
551
|
-
#-------------------------------------------------------------#
|
552
|
-
# Another way to create a flow using the composition operator #
|
553
|
-
#-------------------------------------------------------------#
|
554
|
-
|
555
|
-
SquareAllNumbers =
|
556
|
-
Steps::ConvertTextToNumbers >> Steps::Square
|
557
|
-
|
558
|
-
SquareAllNumbers
|
559
|
-
.call(numbers: %w[1 1 2 2 3 4])
|
560
|
-
.on_success { |value| p value[:numbers] } # [1, 1, 4, 4, 9, 16]
|
561
|
-
|
562
625
|
# Note:
|
563
626
|
# ----
|
564
627
|
# When happening a failure, the use case responsible
|
565
628
|
# will be accessible in the result
|
566
629
|
|
567
|
-
result =
|
630
|
+
result = DoubleAllNumbers.call(numbers: %w[1 1 b 2 3 4])
|
568
631
|
|
569
|
-
result.failure?
|
632
|
+
result.failure? # true
|
570
633
|
result.use_case.is_a?(Steps::ConvertTextToNumbers) # true
|
571
634
|
|
572
635
|
result.on_failure do |_message, use_case|
|
@@ -578,7 +641,7 @@ end
|
|
578
641
|
|
579
642
|
#### Is it possible to compose a use case flow with other ones?
|
580
643
|
|
581
|
-
Answer: Yes, it is.
|
644
|
+
Answer: Yes, it is possible.
|
582
645
|
|
583
646
|
```ruby
|
584
647
|
module Steps
|
@@ -587,9 +650,9 @@ module Steps
|
|
587
650
|
|
588
651
|
def call!
|
589
652
|
if numbers.all? { |value| String(value) =~ /\d+/ }
|
590
|
-
Success
|
653
|
+
Success result: { numbers: numbers.map(&:to_i) }
|
591
654
|
else
|
592
|
-
Failure
|
655
|
+
Failure result: { message: 'numbers must contain only numeric types' }
|
593
656
|
end
|
594
657
|
end
|
595
658
|
end
|
@@ -598,7 +661,7 @@ module Steps
|
|
598
661
|
attribute :numbers
|
599
662
|
|
600
663
|
def call!
|
601
|
-
Success
|
664
|
+
Success result: { numbers: numbers.map { |number| number + 2 } }
|
602
665
|
end
|
603
666
|
end
|
604
667
|
|
@@ -606,7 +669,7 @@ module Steps
|
|
606
669
|
attribute :numbers
|
607
670
|
|
608
671
|
def call!
|
609
|
-
Success
|
672
|
+
Success result: { numbers: numbers.map { |number| number * 2 } }
|
610
673
|
end
|
611
674
|
end
|
612
675
|
|
@@ -614,20 +677,28 @@ module Steps
|
|
614
677
|
attribute :numbers
|
615
678
|
|
616
679
|
def call!
|
617
|
-
Success
|
680
|
+
Success result: { numbers: numbers.map { |number| number * number } }
|
618
681
|
end
|
619
682
|
end
|
620
683
|
end
|
621
684
|
|
622
|
-
|
623
|
-
|
624
|
-
|
685
|
+
DoubleAllNumbers =
|
686
|
+
Micro::Cases.flow([Steps::ConvertTextToNumbers, Steps::Double])
|
687
|
+
|
688
|
+
SquareAllNumbers =
|
689
|
+
Micro::Cases.flow([Steps::ConvertTextToNumbers, Steps::Square])
|
690
|
+
|
691
|
+
DoubleAllNumbersAndAdd2 =
|
692
|
+
Micro::Cases.flow([DoubleAllNumbers, Steps::Add2])
|
625
693
|
|
626
|
-
|
627
|
-
|
694
|
+
SquareAllNumbersAndAdd2 =
|
695
|
+
Micro::Cases.flow([SquareAllNumbers, Steps::Add2])
|
628
696
|
|
629
|
-
SquareAllNumbersAndDouble =
|
630
|
-
|
697
|
+
SquareAllNumbersAndDouble =
|
698
|
+
Micro::Cases.flow([SquareAllNumbersAndAdd2, DoubleAllNumbers])
|
699
|
+
|
700
|
+
DoubleAllNumbersAndSquareAndAdd2 =
|
701
|
+
Micro::Cases.flow([DoubleAllNumbers, SquareAllNumbersAndAdd2])
|
631
702
|
|
632
703
|
SquareAllNumbersAndDouble
|
633
704
|
.call(numbers: %w[1 1 2 2 3 4])
|
@@ -638,16 +709,153 @@ DoubleAllNumbersAndSquareAndAdd2
|
|
638
709
|
.on_success { |value| p value[:numbers] } # [6, 6, 18, 18, 38, 66]
|
639
710
|
```
|
640
711
|
|
641
|
-
Note: You can blend any of the [available syntaxes/approaches](#how-to-create-a-flow-which-has-reusable-steps-to-define-a-complex-use-case) to create use case flows - [examples](https://github.com/serradura/u-case/blob/
|
712
|
+
Note: You can blend any of the [available syntaxes/approaches](#how-to-create-a-flow-which-has-reusable-steps-to-define-a-complex-use-case) to create use case flows - [examples](https://github.com/serradura/u-case/blob/714c6b658fc6aa02617e6833ddee09eddc760f2a/test/micro/cases/flow/blend_test.rb#L5-L35).
|
642
713
|
|
643
714
|
[⬆️ Back to Top](#table-of-contents-)
|
644
715
|
|
645
|
-
#### Is it possible a flow accumulates its input and merges each success result to use as the argument of
|
716
|
+
#### Is it possible a flow accumulates its input and merges each success result to use as the argument of the next use cases?
|
646
717
|
|
647
|
-
Answer: Yes, it is!
|
718
|
+
Answer: Yes, it is possible! Look at the example below to understand how the data accumulation works inside of the flow execution.
|
719
|
+
|
720
|
+
```ruby
|
721
|
+
module Users
|
722
|
+
class FindByEmail < Micro::Case
|
723
|
+
attribute :email
|
724
|
+
|
725
|
+
def call!
|
726
|
+
user = User.find_by(email: email)
|
727
|
+
|
728
|
+
return Success result: { user: user } if user
|
729
|
+
|
730
|
+
Failure(:user_not_found)
|
731
|
+
end
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
module Users
|
736
|
+
class ValidatePassword < Micro::Case::Strict
|
737
|
+
attributes :user, :password
|
738
|
+
|
739
|
+
def call!
|
740
|
+
return Failure(:user_must_be_persisted) if user.new_record?
|
741
|
+
return Failure(:wrong_password) if user.wrong_password?(password)
|
742
|
+
|
743
|
+
return Success result: attributes(:user)
|
744
|
+
end
|
745
|
+
end
|
746
|
+
end
|
747
|
+
|
748
|
+
module Users
|
749
|
+
Authenticate = Micro::Cases.flow([
|
750
|
+
FindByEmail,
|
751
|
+
ValidatePassword
|
752
|
+
])
|
753
|
+
end
|
754
|
+
|
755
|
+
Users::Authenticate
|
756
|
+
.call(email: 'somebody@test.com', password: 'password')
|
757
|
+
.on_success { |result| sign_in(result[:user]) }
|
758
|
+
.on_failure(:wrong_password) { render status: 401 }
|
759
|
+
.on_failure(:user_not_found) { render status: 404 }
|
760
|
+
```
|
761
|
+
|
762
|
+
First, lets see the attributes used by each use case:
|
763
|
+
|
764
|
+
```ruby
|
765
|
+
class Users::FindByEmail < Micro::Case
|
766
|
+
attribute :email
|
767
|
+
end
|
768
|
+
|
769
|
+
class Users::ValidatePassword < Micro::Case
|
770
|
+
attributes :user, :password
|
771
|
+
end
|
772
|
+
```
|
773
|
+
|
774
|
+
As you can see the `Users::ValidatePassword` expects a user as its input. So, how does it receives the user?
|
775
|
+
It receives the user from the `Users::FindByEmail` success result!
|
776
|
+
|
777
|
+
And this, is the power of use cases composition because the output
|
778
|
+
of one step will compose the input of the next use case in the flow!
|
779
|
+
|
780
|
+
> input **>>** process **>>** output
|
781
|
+
|
782
|
+
> **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 sharing their own data.
|
648
783
|
|
649
784
|
[⬆️ Back to Top](#table-of-contents-)
|
650
785
|
|
786
|
+
#### How to understand what is happening during a flow execution?
|
787
|
+
|
788
|
+
Use `Micro::Case::Result#transitions`!
|
789
|
+
|
790
|
+
Let's use the [previous section example](#is-it-possible-a-flow-accumulates-its-input-and-merges-each-success-result-to-use-as-the-argument-of-the-next-use-cases) to ilustrate how to use this feature.
|
791
|
+
|
792
|
+
```ruby
|
793
|
+
user_authenticated =
|
794
|
+
Users::Authenticate.call(email: 'rodrigo@test.com', password: user_password)
|
795
|
+
|
796
|
+
user_authenticated.transitions
|
797
|
+
[
|
798
|
+
{
|
799
|
+
:use_case => {
|
800
|
+
:class => Users::FindByEmail,
|
801
|
+
:attributes => { :email => "rodrigo@test.com" }
|
802
|
+
},
|
803
|
+
:success => {
|
804
|
+
:type => :ok,
|
805
|
+
:result => {
|
806
|
+
:user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
|
807
|
+
}
|
808
|
+
},
|
809
|
+
:accessible_attributes => [ :email, :password ]
|
810
|
+
},
|
811
|
+
{
|
812
|
+
:use_case => {
|
813
|
+
:class => Users::ValidatePassword,
|
814
|
+
:attributes => {
|
815
|
+
:user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
|
816
|
+
:password => "123456"
|
817
|
+
}
|
818
|
+
},
|
819
|
+
:success => {
|
820
|
+
:type => :ok,
|
821
|
+
:result => {
|
822
|
+
:user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
|
823
|
+
}
|
824
|
+
},
|
825
|
+
:accessible_attributes => [ :email, :password, :user ]
|
826
|
+
}
|
827
|
+
]
|
828
|
+
```
|
829
|
+
|
830
|
+
The example above shows the output generated by the `Micro::Case::Result#transitions`.
|
831
|
+
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.
|
832
|
+
|
833
|
+
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).
|
834
|
+
|
835
|
+
> **Note:** The [`Micro::Case::Result#then`](#how-to-use-the-microcaseresultthen-method) increments the `Micro::Case::Result#transitions`.
|
836
|
+
|
837
|
+
PS: Use the `Micro::Case::Result.disable_transition_tracking` feature toggle to disable this feature (use once, because it is global) and increase the use cases' performance.
|
838
|
+
|
839
|
+
##### `Micro::Case::Result#transitions` schema
|
840
|
+
```ruby
|
841
|
+
[
|
842
|
+
{
|
843
|
+
use_case: {
|
844
|
+
class: <Micro::Case>,# Use case which was executed
|
845
|
+
attributes: <Hash> # (Input) The use case's attributes
|
846
|
+
},
|
847
|
+
[success:, failure:] => { # (Output)
|
848
|
+
type: <Symbol>, # Result type. Defaults:
|
849
|
+
# Success = :ok, Failure = :error/:exception
|
850
|
+
result: <Hash> # The data returned by the use case
|
851
|
+
},
|
852
|
+
accessible_attributes: <Array>, # Properties that can be accessed by the use case's attributes,
|
853
|
+
# starting with Hash used to invoke it and which are incremented
|
854
|
+
# with each result value of the flow's use cases.
|
855
|
+
}
|
856
|
+
]
|
857
|
+
```
|
858
|
+
|
651
859
|
#### Is it possible to declare a flow which includes the use case itself?
|
652
860
|
|
653
861
|
Answer: Yes, it is! You can use the `self.call!` macro. e.g:
|
@@ -657,7 +865,7 @@ class ConvertTextToNumber < Micro::Case
|
|
657
865
|
attribute :text
|
658
866
|
|
659
867
|
def call!
|
660
|
-
Success
|
868
|
+
Success result: { number: text.to_i }
|
661
869
|
end
|
662
870
|
end
|
663
871
|
|
@@ -665,7 +873,7 @@ class ConvertNumberToText < Micro::Case
|
|
665
873
|
attribute :number
|
666
874
|
|
667
875
|
def call!
|
668
|
-
Success
|
876
|
+
Success result: { text: number.to_s }
|
669
877
|
end
|
670
878
|
end
|
671
879
|
|
@@ -677,17 +885,17 @@ class Double < Micro::Case
|
|
677
885
|
attribute :number
|
678
886
|
|
679
887
|
def call!
|
680
|
-
Success
|
888
|
+
Success result: { number: number * 2 }
|
681
889
|
end
|
682
890
|
end
|
683
891
|
|
684
892
|
result = Double.call(text: '4')
|
685
893
|
|
686
894
|
result.success? # true
|
687
|
-
result
|
895
|
+
result[:number] # "8"
|
688
896
|
|
689
897
|
# NOTE: This feature can be used with the Micro::Case::Safe.
|
690
|
-
# Checkout
|
898
|
+
# Checkout this test: https://github.com/serradura/u-case/blob/714c6b658fc6aa02617e6833ddee09eddc760f2a/test/micro/case/safe/with_inner_flow_test.rb
|
691
899
|
```
|
692
900
|
|
693
901
|
[⬆️ Back to Top](#table-of-contents-)
|
@@ -701,7 +909,7 @@ class Double < Micro::Case::Strict
|
|
701
909
|
attribute :numbers
|
702
910
|
|
703
911
|
def call!
|
704
|
-
Success
|
912
|
+
Success result: { numbers: numbers.map { |number| number * 2 } }
|
705
913
|
end
|
706
914
|
end
|
707
915
|
|
@@ -715,7 +923,7 @@ Double.call({})
|
|
715
923
|
|
716
924
|
### `Micro::Case::Safe` - Is there some feature to auto handle exceptions inside of a use case or flow?
|
717
925
|
|
718
|
-
Answer: Yes, there is!
|
926
|
+
Answer: Yes, there is one!
|
719
927
|
|
720
928
|
**Use cases:**
|
721
929
|
|
@@ -730,14 +938,18 @@ class Divide < Micro::Case::Safe
|
|
730
938
|
attributes :a, :b
|
731
939
|
|
732
940
|
def call!
|
733
|
-
|
734
|
-
|
941
|
+
if a.is_a?(Integer) && b.is_a?(Integer)
|
942
|
+
Success result: { number: a / b}
|
943
|
+
else
|
944
|
+
Failure(:not_an_integer)
|
945
|
+
end
|
735
946
|
end
|
736
947
|
end
|
737
948
|
|
738
949
|
result = Divide.call(a: 2, b: 0)
|
739
|
-
result.type == :exception
|
740
|
-
result.
|
950
|
+
result.type == :exception # true
|
951
|
+
result.data # { exception: #<ZeroDivisionError...> }
|
952
|
+
result[:exception].is_a?(ZeroDivisionError) # true
|
741
953
|
|
742
954
|
result.on_failure(:exception) do |exception|
|
743
955
|
AppLogger.error(exception.message) # E, [2019-08-21T00:05:44.195506 #9532] ERROR -- : divided by 0
|
@@ -758,25 +970,18 @@ end
|
|
758
970
|
# Another note:
|
759
971
|
# ------------
|
760
972
|
# It is possible to rescue an exception even when is a safe use case.
|
761
|
-
# Examples: https://github.com/serradura/u-case/blob/
|
973
|
+
# Examples: https://github.com/serradura/u-case/blob/714c6b658fc6aa02617e6833ddee09eddc760f2a/test/micro/case/safe_test.rb#L90-L118
|
762
974
|
```
|
763
975
|
|
764
|
-
|
976
|
+
[⬆️ Back to Top](#table-of-contents-)
|
977
|
+
|
978
|
+
#### `Micro::Cases::Safe::Flow`
|
765
979
|
|
766
980
|
As the safe use cases, safe flows can intercept an exception in any of its steps. These are the ways to define one:
|
767
981
|
|
768
982
|
```ruby
|
769
983
|
module Users
|
770
|
-
Create =
|
771
|
-
end
|
772
|
-
|
773
|
-
# Note:
|
774
|
-
# 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
|
775
|
-
|
776
|
-
# The alternatives to declare a safe flow are:
|
777
|
-
|
778
|
-
module Users
|
779
|
-
Create = Micro::Case::Safe::Flow([
|
984
|
+
Create = Micro::Cases.safe_flow([
|
780
985
|
ProcessParams,
|
781
986
|
ValidateParams,
|
782
987
|
Persist,
|
@@ -794,33 +999,63 @@ module Users
|
|
794
999
|
SendToCRM
|
795
1000
|
end
|
796
1001
|
end
|
1002
|
+
```
|
797
1003
|
|
1004
|
+
[⬆️ Back to Top](#table-of-contents-)
|
798
1005
|
|
799
|
-
|
800
|
-
# ! Deprecated: Micro::Case::Safe::Flow mixin ! #
|
801
|
-
# !-------------------------------------------! #
|
1006
|
+
#### `Micro::Case::Result#on_exception`
|
802
1007
|
|
803
|
-
|
804
|
-
# Deprecation: Micro::Case::Flow mixin is being deprecated, please use `Micro::Case` inheritance instead.
|
1008
|
+
In functional programming errors/exceptions are handled as regular data, the idea is to transform the output even when it happens an unexpected behavior. For many, [exceptions are very similar to the GOTO statement](https://softwareengineering.stackexchange.com/questions/189222/are-exceptions-as-control-flow-considered-a-serious-antipattern-if-so-why), jumping the application flow to paths which could be difficult to figure out how things work in a system.
|
805
1009
|
|
806
|
-
|
807
|
-
|
808
|
-
|
1010
|
+
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.
|
1011
|
+
|
1012
|
+
> **Note**: this feature will work better if you use it with a `Micro::Case::Safe` use case/flow.
|
809
1013
|
|
810
|
-
|
1014
|
+
How does it work?
|
1015
|
+
|
1016
|
+
```ruby
|
1017
|
+
class Divide < Micro::Case::Safe
|
1018
|
+
attributes :a, :b
|
1019
|
+
|
1020
|
+
def call!
|
1021
|
+
Success result: { division: a / b }
|
811
1022
|
end
|
812
1023
|
end
|
813
1024
|
|
814
|
-
|
1025
|
+
Divide
|
1026
|
+
.call(a: 2, b: 0)
|
1027
|
+
.on_success { |result| puts result[:division] }
|
1028
|
+
.on_exception(TypeError) { puts 'Please, use only numeric attributes.' }
|
1029
|
+
.on_exception(ZeroDivisionError) { |_error| puts "Can't divide a number by 0." }
|
1030
|
+
.on_exception { |_error, _use_case| puts 'Oh no, something went wrong!' }
|
1031
|
+
|
1032
|
+
# Output:
|
1033
|
+
# -------
|
1034
|
+
# Can't divide a number by 0
|
1035
|
+
# Oh no, something went wrong!
|
1036
|
+
|
1037
|
+
Divide.
|
1038
|
+
.call(a: 2, b: '2').
|
1039
|
+
.on_success { |result| puts result[:division] }
|
1040
|
+
.on_exception(TypeError) { puts 'Please, use only numeric attributes.' }
|
1041
|
+
.on_exception(ZeroDivisionError) { |_error| puts "Can't divide a number by 0." }
|
1042
|
+
.on_exception { |_error, _use_case| puts 'Oh no, something went wrong!' }
|
1043
|
+
|
1044
|
+
# Output:
|
1045
|
+
# -------
|
1046
|
+
# Please, use only numeric attributes.
|
1047
|
+
# Oh no, something went wrong!
|
815
1048
|
```
|
816
1049
|
|
1050
|
+
As you can see, this hook has the same behavior of `result.on_failure(:exception)`, but, the ideia here is to have a better communication in the code, making an explicit reference when some failure happened because of an exception.
|
1051
|
+
|
817
1052
|
[⬆️ Back to Top](#table-of-contents-)
|
818
1053
|
|
819
|
-
### `u-case/
|
1054
|
+
### `u-case/with_activemodel_validation` - How to validate use case attributes?
|
820
1055
|
|
821
1056
|
**Requirement:**
|
822
1057
|
|
823
|
-
To do this your application must have the [activemodel >= 3.2](https://rubygems.org/gems/activemodel) as a dependency.
|
1058
|
+
To do this your application must have the [activemodel >= 3.2, < 6.1.0](https://rubygems.org/gems/activemodel) as a dependency.
|
824
1059
|
|
825
1060
|
```ruby
|
826
1061
|
#
|
@@ -833,9 +1068,9 @@ class Multiply < Micro::Case
|
|
833
1068
|
validates :a, :b, presence: true, numericality: true
|
834
1069
|
|
835
1070
|
def call!
|
836
|
-
return Failure
|
1071
|
+
return Failure :validation_error, result: { errors: self.errors } if invalid?
|
837
1072
|
|
838
|
-
Success
|
1073
|
+
Success result: { number: a * b }
|
839
1074
|
end
|
840
1075
|
end
|
841
1076
|
|
@@ -844,10 +1079,10 @@ end
|
|
844
1079
|
# your use cases on validation errors, you can use:
|
845
1080
|
|
846
1081
|
# In some file. e.g: A Rails initializer
|
847
|
-
require 'u-case/
|
1082
|
+
require 'u-case/with_activemodel_validation' # or require 'micro/case/with_validation'
|
848
1083
|
|
849
1084
|
# In the Gemfile
|
850
|
-
gem 'u-case', require: 'u-case/
|
1085
|
+
gem 'u-case', require: 'u-case/with_activemodel_validation'
|
851
1086
|
|
852
1087
|
# Using this approach, you can rewrite the previous example with less code. e.g:
|
853
1088
|
|
@@ -857,7 +1092,7 @@ class Multiply < Micro::Case
|
|
857
1092
|
validates :a, :b, presence: true, numericality: true
|
858
1093
|
|
859
1094
|
def call!
|
860
|
-
Success
|
1095
|
+
Success result: { number: a * b }
|
861
1096
|
end
|
862
1097
|
end
|
863
1098
|
|
@@ -869,10 +1104,10 @@ end
|
|
869
1104
|
|
870
1105
|
#### If I enabled the auto validation, is it possible to disable it only in specific use case classes?
|
871
1106
|
|
872
|
-
Answer: Yes, it is. To do this, you only need to use the `disable_auto_validation` macro. e.g:
|
1107
|
+
Answer: Yes, it is possible. To do this, you only need to use the `disable_auto_validation` macro. e.g:
|
873
1108
|
|
874
1109
|
```ruby
|
875
|
-
require 'u-case/
|
1110
|
+
require 'u-case/with_activemodel_validation'
|
876
1111
|
|
877
1112
|
class Multiply < Micro::Case
|
878
1113
|
disable_auto_validation
|
@@ -882,7 +1117,7 @@ class Multiply < Micro::Case
|
|
882
1117
|
validates :a, :b, presence: true, numericality: true
|
883
1118
|
|
884
1119
|
def call!
|
885
|
-
Success
|
1120
|
+
Success result: { number: a * b }
|
886
1121
|
end
|
887
1122
|
end
|
888
1123
|
|
@@ -894,168 +1129,190 @@ Multiply.call(a: 2, b: 'a')
|
|
894
1129
|
|
895
1130
|
[⬆️ Back to Top](#table-of-contents-)
|
896
1131
|
|
1132
|
+
#### `Kind::Validator`
|
1133
|
+
|
1134
|
+
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).
|
1135
|
+
|
1136
|
+
The example below shows how to validate the attributes data types.
|
1137
|
+
|
1138
|
+
```ruby
|
1139
|
+
class Todo::List::AddItem < Micro::Case
|
1140
|
+
attributes :user, :params
|
1141
|
+
|
1142
|
+
validates :user, kind: User
|
1143
|
+
validates :params, kind: ActionController::Parameters
|
1144
|
+
|
1145
|
+
def call!
|
1146
|
+
todo_params = params.require(:todo).permit(:title, :due_at)
|
1147
|
+
|
1148
|
+
todo = user.todos.create(todo_params)
|
1149
|
+
|
1150
|
+
Success result: { todo: todo }
|
1151
|
+
rescue ActionController::ParameterMissing => e
|
1152
|
+
Failure :parameter_missing, result: { message: e.message }
|
1153
|
+
end
|
1154
|
+
end
|
1155
|
+
```
|
1156
|
+
|
897
1157
|
## Benchmarks
|
898
1158
|
|
899
|
-
### `Micro::Case`
|
1159
|
+
### `Micro::Case` (v2.6.0)
|
900
1160
|
|
901
1161
|
#### Best overall
|
902
1162
|
|
903
1163
|
The table below contains the average between the [Success results](#success-results) and [Failure results](#failure-results) benchmarks.
|
904
1164
|
|
905
|
-
| Gem / Abstraction | Iterations per second | Comparison
|
906
|
-
| ---------------------- | --------------------: |
|
907
|
-
| **Micro::Case** |
|
908
|
-
| Dry::Monads |
|
909
|
-
| Interactor |
|
910
|
-
| Trailblazer::Operation |
|
911
|
-
| Dry::Transaction |
|
1165
|
+
| Gem / Abstraction | Iterations per second | Comparison |
|
1166
|
+
| ---------------------- | --------------------: | ----------------: |
|
1167
|
+
| **Micro::Case** | 105124.3 | _**The Fastest**_ |
|
1168
|
+
| Dry::Monads | 103290.1 | 0.02x slower |
|
1169
|
+
| Interactor | 21342.3 | 4.93x slower |
|
1170
|
+
| Trailblazer::Operation | 14652.7 | 7.17x slower |
|
1171
|
+
| Dry::Transaction | 5310.3 | 19.80x slower |
|
912
1172
|
|
913
1173
|
---
|
914
1174
|
|
915
1175
|
#### Success results
|
916
1176
|
|
917
|
-
| Gem / Abstraction | Iterations per second | Comparison
|
918
|
-
| ----------------- | --------------------: |
|
919
|
-
| Dry::Monads |
|
920
|
-
| **Micro::Case** |
|
921
|
-
| Interactor |
|
922
|
-
| Trailblazer::Operation |
|
923
|
-
| Dry::Transaction |
|
1177
|
+
| Gem / Abstraction | Iterations per second | Comparison |
|
1178
|
+
| ----------------- | --------------------: | ----------------: |
|
1179
|
+
| Dry::Monads | 134801.0 | _**The Fastest**_ |
|
1180
|
+
| **Micro::Case** | 105909.2 | 1.27x slower |
|
1181
|
+
| Interactor | 29458.2 | 4.58x slower |
|
1182
|
+
| Trailblazer::Operation | 14714.9 | 9.16x slower |
|
1183
|
+
| Dry::Transaction | 5642.6 | 28.89x slower |
|
924
1184
|
|
925
1185
|
<details>
|
926
1186
|
<summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
|
927
1187
|
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
```
|
1188
|
+
```ruby
|
1189
|
+
# Warming up --------------------------------------
|
1190
|
+
# Interactor 2.897k i/100ms
|
1191
|
+
# Trailblazer::Operation 1.494k i/100ms
|
1192
|
+
# Dry::Monads 13.854k i/100ms
|
1193
|
+
# Dry::Transaction 561.000 i/100ms
|
1194
|
+
# Micro::Case 10.523k i/100ms
|
1195
|
+
# Micro::Case::Strict 7.982k i/100ms
|
1196
|
+
# Micro::Case::Safe 10.568k i/100ms
|
1197
|
+
|
1198
|
+
# Calculating -------------------------------------
|
1199
|
+
# Interactor 29.458k (± 3.4%) i/s - 147.747k in 5.021405s
|
1200
|
+
# Trailblazer::Operation 14.715k (± 1.8%) i/s - 74.700k in 5.078128s
|
1201
|
+
# Dry::Monads 134.801k (± 8.7%) i/s - 678.846k in 5.088739s
|
1202
|
+
# Dry::Transaction 5.643k (± 2.1%) i/s - 28.611k in 5.072969s
|
1203
|
+
# Micro::Case 105.909k (± 2.4%) i/s - 536.673k in 5.070329s
|
1204
|
+
# Micro::Case::Strict 84.234k (± 1.5%) i/s - 423.046k in 5.023447s
|
1205
|
+
# Micro::Case::Safe 105.725k (± 1.9%) i/s - 538.968k in 5.099817s
|
1206
|
+
|
1207
|
+
# Comparison:
|
1208
|
+
# Dry::Monads: 134801.0 i/s
|
1209
|
+
# Micro::Case: 105909.2 i/s - 1.27x (± 0.00) slower
|
1210
|
+
# Micro::Case::Safe: 105725.0 i/s - 1.28x (± 0.00) slower
|
1211
|
+
# Micro::Case::Strict: 84234.4 i/s - 1.60x (± 0.00) slower
|
1212
|
+
# Interactor: 29458.2 i/s - 4.58x (± 0.00) slower
|
1213
|
+
# Trailblazer::Operation: 14714.9 i/s - 9.16x (± 0.00) slower
|
1214
|
+
# Dry::Transaction: 5642.6 i/s - 23.89x (± 0.00) slower
|
1215
|
+
```
|
957
1216
|
</details>
|
958
1217
|
|
959
1218
|
https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_success_result.rb
|
960
1219
|
|
961
1220
|
#### Failure results
|
962
1221
|
|
963
|
-
| Gem / Abstraction | Iterations per second | Comparison
|
964
|
-
| ----------------- | --------------------: |
|
965
|
-
| **Micro::Case** |
|
966
|
-
| Dry::Monads |
|
967
|
-
| Trailblazer::Operation |
|
968
|
-
| Interactor |
|
969
|
-
| Dry::Transaction |
|
1222
|
+
| Gem / Abstraction | Iterations per second | Comparison |
|
1223
|
+
| ----------------- | --------------------: | ----------------: |
|
1224
|
+
| **Micro::Case** | 104339.4 | _**The Fastest**_ |
|
1225
|
+
| Dry::Monads | 71779.2 | 1.45x slower |
|
1226
|
+
| Trailblazer::Operation | 14590.6 | 7.15x slower |
|
1227
|
+
| Interactor | 13226.5 | 7.89x slower |
|
1228
|
+
| Dry::Transaction | 4978.1 | 20.96x slower |
|
970
1229
|
|
971
1230
|
<details>
|
972
1231
|
<summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
|
973
1232
|
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
```
|
1233
|
+
```ruby
|
1234
|
+
# Warming up --------------------------------------
|
1235
|
+
# Interactor 1.339k i/100ms
|
1236
|
+
# Trailblazer::Operation 1.393k i/100ms
|
1237
|
+
# Dry::Monads 7.208k i/100ms
|
1238
|
+
# Dry::Transaction 423.000 i/100ms
|
1239
|
+
# Micro::Case 9.620k i/100ms
|
1240
|
+
# Micro::Case::Strict 8.238k i/100ms
|
1241
|
+
# Micro::Case::Safe 9.906k i/100ms
|
1242
|
+
|
1243
|
+
# Calculating -------------------------------------
|
1244
|
+
# Interactor 13.227k (± 3.3%) i/s - 66.950k in 5.067145s
|
1245
|
+
# Trailblazer::Operation 14.591k (± 4.0%) i/s - 73.829k in 5.069162s
|
1246
|
+
# Dry::Monads 71.779k (± 2.5%) i/s - 360.400k in 5.024294s
|
1247
|
+
# Dry::Transaction 4.978k (± 3.3%) i/s - 24.957k in 5.019153s
|
1248
|
+
# Micro::Case 103.957k (± 1.8%) i/s - 529.100k in 5.091221s
|
1249
|
+
# Micro::Case::Strict 83.094k (± 2.0%) i/s - 420.138k in 5.058233s
|
1250
|
+
# Micro::Case::Safe 104.339k (± 1.7%) i/s - 525.018k in 5.033381s
|
1251
|
+
|
1252
|
+
# Comparison:
|
1253
|
+
# Micro::Case::Safe: 104339.4 i/s
|
1254
|
+
# Micro::Case: 103957.2 i/s - same-ish: difference falls within error
|
1255
|
+
# Micro::Case::Strict: 83094.5 i/s - 1.26x (± 0.00) slower
|
1256
|
+
# Dry::Monads: 71779.2 i/s - 1.45x (± 0.00) slower
|
1257
|
+
# Trailblazer::Operation: 14590.6 i/s - 7.15x (± 0.00) slower
|
1258
|
+
# Interactor: 13226.5 i/s - 7.89x (± 0.00) slower
|
1259
|
+
# Dry::Transaction: 4978.1 i/s - 20.96x (± 0.00) slower
|
1260
|
+
```
|
1003
1261
|
</details>
|
1004
1262
|
|
1005
1263
|
https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_failure_result.rb
|
1006
1264
|
|
1007
1265
|
---
|
1008
1266
|
|
1009
|
-
### `Micro::Case::Flow`
|
1267
|
+
### `Micro::Case::Flow` (v2.6.0)
|
1010
1268
|
|
1011
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) |
|
1012
|
-
| ------------------ |
|
1013
|
-
| Micro::Case::Flow | _**The
|
1014
|
-
| Micro::Case::Safe::Flow | 0x slower
|
1015
|
-
| Interactor::Organizer | 1.
|
1270
|
+
| ------------------ | ----------------: | ----------------: |
|
1271
|
+
| Micro::Case::Flow | _**The Fastest**_ | _**The Fastest**_ |
|
1272
|
+
| Micro::Case::Safe::Flow | 0x slower | 0x slower |
|
1273
|
+
| Interactor::Organizer | 1.27x slower | 5.48x slower |
|
1016
1274
|
|
1017
1275
|
\* The `Dry::Monads`, `Dry::Transaction`, `Trailblazer::Operation` are out of this analysis because all of them doesn't have this kind of feature.
|
1018
1276
|
|
1019
1277
|
<details>
|
1020
1278
|
<summary><strong>Success results</strong> - Show the full benchmark/ips results.</summary>
|
1021
1279
|
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
```
|
1280
|
+
```ruby
|
1281
|
+
# Warming up --------------------------------------
|
1282
|
+
# Interactor::Organizer 4.765k i/100ms
|
1283
|
+
# Micro::Case::Flow 5.372k i/100ms
|
1284
|
+
# Micro::Case::Safe::Flow 5.855k i/100ms
|
1285
|
+
# Calculating -------------------------------------
|
1286
|
+
# Interactor::Organizer 48.598k (± 5.2%) i/s - 243.015k in 5.014307s
|
1287
|
+
# Micro::Case::Flow 61.606k (± 4.4%) i/s - 311.576k in 5.068602s
|
1288
|
+
# Micro::Case::Safe::Flow 60.688k (± 4.8%) i/s - 304.460k in 5.028877s
|
1289
|
+
|
1290
|
+
# Comparison:
|
1291
|
+
# Micro::Case::Flow: 61606.3 i/s
|
1292
|
+
# Micro::Case::Safe::Flow: 60688.3 i/s - same-ish: difference falls within error
|
1293
|
+
# Interactor::Organizer: 48598.2 i/s - 1.27x slower\
|
1294
|
+
```
|
1038
1295
|
</details>
|
1039
1296
|
|
1040
1297
|
<details>
|
1041
1298
|
<summary><strong>Failure results</strong> - Show the full benchmark/ips results.</summary>
|
1042
1299
|
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1300
|
+
```ruby
|
1301
|
+
# Warming up --------------------------------------
|
1302
|
+
# Interactor::Organizer 2.209k i/100ms
|
1303
|
+
# Micro::Case::Flow 11.508k i/100ms
|
1304
|
+
# Micro::Case::Safe::Flow 11.605k i/100ms
|
1305
|
+
|
1306
|
+
# Calculating -------------------------------------
|
1307
|
+
# Interactor::Organizer 22.592k (± 2.8%) i/s - 114.868k in 5.088685s
|
1308
|
+
# Micro::Case::Flow 123.629k (± 2.9%) i/s - 621.432k in 5.030844s
|
1309
|
+
# Micro::Case::Safe::Flow 123.862k (± 3.0%) i/s - 626.670k in 5.064097s
|
1310
|
+
|
1311
|
+
# Comparison:
|
1312
|
+
# Micro::Case::Safe::Flow: 123862.4 i/s
|
1313
|
+
# Micro::Case::Flow: 123629.3 i/s - same-ish: difference falls within error
|
1314
|
+
# Interactor::Organizer: 22592.2 i/s - 5.48x slower
|
1315
|
+
```
|
1059
1316
|
</details>
|
1060
1317
|
|
1061
1318
|
https://github.com/serradura/u-case/tree/master/benchmarks/flow
|