u-case 2.3.0 → 3.0.0.rc1
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 +8 -2
- data/.travis.yml +1 -0
- data/Gemfile +22 -1
- data/README.md +586 -331
- data/lib/micro/case.rb +72 -57
- data/lib/micro/case/error.rb +17 -12
- data/lib/micro/case/result.rb +103 -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} +8 -6
- 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/lib/u-case/with_validation.rb +4 -1
- data/u-case.gemspec +3 -14
- metadata +26 -10
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1785653f481f4622720634abe0448ef3d41dd522473ca88e8dc75d987ddd4413
|
4
|
+
data.tar.gz: f497efd7bc60619a86a3b8bb22e15551a4b513fbe4088e92a1718bd6568ec6df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d9d77becd6c184bd58765df5d9dbbad8cee87054e6380b259e3463117401d1107cce3549a3a9626cc97f1307766b4aaef40cc32b5a7744a288790fcae6f1ee97
|
7
|
+
data.tar.gz: b65306eb69ee9a8fe7dc39d943517aca4617181ddb2c473b6ac241edf4674634b1c7517e0f3c41ad14d243c13f7c83a782e9f6971b1212fd1c87cdb0ef14bf2d
|
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
|
@@ -11,3 +9,11 @@ if [[ ! $ruby_v =~ '2.2.0' ]]; then
|
|
11
9
|
ACTIVEMODEL_VERSION='5.2' bundle update
|
12
10
|
ACTIVEMODEL_VERSION='5.2' bundle exec rake test
|
13
11
|
fi
|
12
|
+
|
13
|
+
if [[ $ruby_v =~ '2.5.' ]] || [[ $ruby_v =~ '2.6.' ]] || [[ $ruby_v =~ '2.7.' ]]; then
|
14
|
+
ACTIVEMODEL_VERSION='6.0' bundle update
|
15
|
+
ACTIVEMODEL_VERSION='6.0' bundle exec rake test
|
16
|
+
fi
|
17
|
+
|
18
|
+
bundle update
|
19
|
+
bundle exec rake test
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
@@ -7,6 +7,7 @@ activemodel_version = ENV.fetch('ACTIVEMODEL_VERSION', '6.1.0')
|
|
7
7
|
activemodel = case activemodel_version
|
8
8
|
when '3.2' then '3.2.22'
|
9
9
|
when '5.2' then '5.2.3'
|
10
|
+
when '6.0' then '6.0.2'
|
10
11
|
end
|
11
12
|
|
12
13
|
if activemodel_version < '6.1.0'
|
@@ -17,7 +18,27 @@ end
|
|
17
18
|
group :test do
|
18
19
|
gem 'minitest', activemodel_version < '4.1' ? '~> 4.2' : '~> 5.0'
|
19
20
|
gem 'simplecov', require: false
|
20
|
-
|
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}"
|
21
42
|
end
|
22
43
|
|
23
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
|
+
Unreleased | 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, expose `Kind.of.Micro::Case::Result` type checker and 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.
|
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,67 @@ 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
|
+
return Success result: { sum: a + b } if Kind.of.Numeric?(a, b)
|
512
|
+
|
513
|
+
Failure(:attributes_arent_numbers)
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
# --
|
518
|
+
|
519
|
+
success_result =
|
520
|
+
Add
|
521
|
+
.call(a: 2, b: 2)
|
522
|
+
.then { |result| result.success? ? result[:sum] : 0 }
|
523
|
+
|
524
|
+
puts success_result # 4
|
525
|
+
|
526
|
+
# --
|
527
|
+
|
528
|
+
failure_result =
|
529
|
+
Add
|
530
|
+
.call(a: 2, b: '2')
|
531
|
+
.then { |result| result.success? ? result[:sum] : 0 }
|
532
|
+
|
533
|
+
puts failure_result # 0
|
534
|
+
```
|
535
|
+
|
462
536
|
[⬆️ Back to Top](#table-of-contents-)
|
463
537
|
|
464
|
-
|
538
|
+
##### How to make attributes data injection using this feature?
|
465
539
|
|
466
|
-
|
540
|
+
Pass a Hash as the second argument of the `Micro::Case::Result#then` method.
|
541
|
+
|
542
|
+
```ruby
|
543
|
+
Todo::FindAllForUser
|
544
|
+
.call(user: current_user, params: params)
|
545
|
+
.then(Paginate)
|
546
|
+
.then(Serialize::PaginatedRelationAsJson, serializer: Todo::Serializer)
|
547
|
+
.on_success { |result| render_json(200, data: result[:todos]) }
|
548
|
+
```
|
549
|
+
|
550
|
+
[⬆️ Back to Top](#table-of-contents-)
|
551
|
+
|
552
|
+
### `Micro::Cases::Flow` - How to compose use cases?
|
553
|
+
|
554
|
+
In this case, this will be a **flow** (`Micro::Cases::Flow`).
|
467
555
|
The main idea of this feature is to use/reuse use cases as steps of a new use case.
|
468
556
|
|
469
557
|
```ruby
|
@@ -473,9 +561,9 @@ module Steps
|
|
473
561
|
|
474
562
|
def call!
|
475
563
|
if numbers.all? { |value| String(value) =~ /\d+/ }
|
476
|
-
Success
|
564
|
+
Success result: { numbers: numbers.map(&:to_i) }
|
477
565
|
else
|
478
|
-
Failure
|
566
|
+
Failure result: { message: 'numbers must contain only numeric types' }
|
479
567
|
end
|
480
568
|
end
|
481
569
|
end
|
@@ -484,7 +572,7 @@ module Steps
|
|
484
572
|
attribute :numbers
|
485
573
|
|
486
574
|
def call!
|
487
|
-
Success
|
575
|
+
Success result: { numbers: numbers.map { |number| number + 2 } }
|
488
576
|
end
|
489
577
|
end
|
490
578
|
|
@@ -492,7 +580,7 @@ module Steps
|
|
492
580
|
attribute :numbers
|
493
581
|
|
494
582
|
def call!
|
495
|
-
Success
|
583
|
+
Success result: { numbers: numbers.map { |number| number * 2 } }
|
496
584
|
end
|
497
585
|
end
|
498
586
|
|
@@ -500,24 +588,24 @@ module Steps
|
|
500
588
|
attribute :numbers
|
501
589
|
|
502
590
|
def call!
|
503
|
-
Success
|
591
|
+
Success result: { numbers: numbers.map { |number| number * number } }
|
504
592
|
end
|
505
593
|
end
|
506
594
|
end
|
507
595
|
|
508
|
-
|
509
|
-
# Creating a flow using
|
510
|
-
|
596
|
+
#-------------------------------------------#
|
597
|
+
# Creating a flow using Micro::Cases.flow() #
|
598
|
+
#-------------------------------------------#
|
511
599
|
|
512
|
-
Add2ToAllNumbers = Micro::
|
600
|
+
Add2ToAllNumbers = Micro::Cases.flow([
|
513
601
|
Steps::ConvertTextToNumbers,
|
514
602
|
Steps::Add2
|
515
603
|
])
|
516
604
|
|
517
605
|
result = Add2ToAllNumbers.call(numbers: %w[1 1 2 2 3 4])
|
518
606
|
|
519
|
-
|
520
|
-
|
607
|
+
result.success? # true
|
608
|
+
result.data # {:numbers => [3, 3, 4, 4, 5, 6]}
|
521
609
|
|
522
610
|
#---------------------------------------------------#
|
523
611
|
# An alternative way to create a flow using classes #
|
@@ -532,41 +620,14 @@ DoubleAllNumbers
|
|
532
620
|
.call(numbers: %w[1 1 b 2 3 4])
|
533
621
|
.on_failure { |message| p message } # "numbers must contain only numeric types"
|
534
622
|
|
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
623
|
# Note:
|
563
624
|
# ----
|
564
625
|
# When happening a failure, the use case responsible
|
565
626
|
# will be accessible in the result
|
566
627
|
|
567
|
-
result =
|
628
|
+
result = DoubleAllNumbers.call(numbers: %w[1 1 b 2 3 4])
|
568
629
|
|
569
|
-
result.failure?
|
630
|
+
result.failure? # true
|
570
631
|
result.use_case.is_a?(Steps::ConvertTextToNumbers) # true
|
571
632
|
|
572
633
|
result.on_failure do |_message, use_case|
|
@@ -578,7 +639,7 @@ end
|
|
578
639
|
|
579
640
|
#### Is it possible to compose a use case flow with other ones?
|
580
641
|
|
581
|
-
Answer: Yes, it is.
|
642
|
+
Answer: Yes, it is possible.
|
582
643
|
|
583
644
|
```ruby
|
584
645
|
module Steps
|
@@ -587,9 +648,9 @@ module Steps
|
|
587
648
|
|
588
649
|
def call!
|
589
650
|
if numbers.all? { |value| String(value) =~ /\d+/ }
|
590
|
-
Success
|
651
|
+
Success result: { numbers: numbers.map(&:to_i) }
|
591
652
|
else
|
592
|
-
Failure
|
653
|
+
Failure result: { message: 'numbers must contain only numeric types' }
|
593
654
|
end
|
594
655
|
end
|
595
656
|
end
|
@@ -598,7 +659,7 @@ module Steps
|
|
598
659
|
attribute :numbers
|
599
660
|
|
600
661
|
def call!
|
601
|
-
Success
|
662
|
+
Success result: { numbers: numbers.map { |number| number + 2 } }
|
602
663
|
end
|
603
664
|
end
|
604
665
|
|
@@ -606,7 +667,7 @@ module Steps
|
|
606
667
|
attribute :numbers
|
607
668
|
|
608
669
|
def call!
|
609
|
-
Success
|
670
|
+
Success result: { numbers: numbers.map { |number| number * 2 } }
|
610
671
|
end
|
611
672
|
end
|
612
673
|
|
@@ -614,20 +675,28 @@ module Steps
|
|
614
675
|
attribute :numbers
|
615
676
|
|
616
677
|
def call!
|
617
|
-
Success
|
678
|
+
Success result: { numbers: numbers.map { |number| number * number } }
|
618
679
|
end
|
619
680
|
end
|
620
681
|
end
|
621
682
|
|
622
|
-
|
623
|
-
|
624
|
-
|
683
|
+
DoubleAllNumbers =
|
684
|
+
Micro::Cases.flow([Steps::ConvertTextToNumbers, Steps::Double])
|
685
|
+
|
686
|
+
SquareAllNumbers =
|
687
|
+
Micro::Cases.flow([Steps::ConvertTextToNumbers, Steps::Square])
|
688
|
+
|
689
|
+
DoubleAllNumbersAndAdd2 =
|
690
|
+
Micro::Cases.flow([DoubleAllNumbers, Steps::Add2])
|
625
691
|
|
626
|
-
|
627
|
-
|
692
|
+
SquareAllNumbersAndAdd2 =
|
693
|
+
Micro::Cases.flow([SquareAllNumbers, Steps::Add2])
|
628
694
|
|
629
|
-
SquareAllNumbersAndDouble =
|
630
|
-
|
695
|
+
SquareAllNumbersAndDouble =
|
696
|
+
Micro::Cases.flow([SquareAllNumbersAndAdd2, DoubleAllNumbers])
|
697
|
+
|
698
|
+
DoubleAllNumbersAndSquareAndAdd2 =
|
699
|
+
Micro::Cases.flow([DoubleAllNumbers, SquareAllNumbersAndAdd2])
|
631
700
|
|
632
701
|
SquareAllNumbersAndDouble
|
633
702
|
.call(numbers: %w[1 1 2 2 3 4])
|
@@ -638,16 +707,153 @@ DoubleAllNumbersAndSquareAndAdd2
|
|
638
707
|
.on_success { |value| p value[:numbers] } # [6, 6, 18, 18, 38, 66]
|
639
708
|
```
|
640
709
|
|
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/
|
710
|
+
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
711
|
|
643
712
|
[⬆️ Back to Top](#table-of-contents-)
|
644
713
|
|
645
|
-
#### Is it possible a flow accumulates its input and merges each success result to use as the argument of
|
714
|
+
#### Is it possible a flow accumulates its input and merges each success result to use as the argument of the next use cases?
|
715
|
+
|
716
|
+
Answer: Yes, it is possible! Look at the example below to understand how the data accumulation works inside of the flow execution.
|
717
|
+
|
718
|
+
```ruby
|
719
|
+
module Users
|
720
|
+
class FindByEmail < Micro::Case
|
721
|
+
attribute :email
|
722
|
+
|
723
|
+
def call!
|
724
|
+
user = User.find_by(email: email)
|
725
|
+
|
726
|
+
return Success result: { user: user } if user
|
727
|
+
|
728
|
+
Failure(:user_not_found)
|
729
|
+
end
|
730
|
+
end
|
731
|
+
end
|
732
|
+
|
733
|
+
module Users
|
734
|
+
class ValidatePassword < Micro::Case::Strict
|
735
|
+
attributes :user, :password
|
736
|
+
|
737
|
+
def call!
|
738
|
+
return Failure(:user_must_be_persisted) if user.new_record?
|
739
|
+
return Failure(:wrong_password) if user.wrong_password?(password)
|
740
|
+
|
741
|
+
return Success result: attributes(:user)
|
742
|
+
end
|
743
|
+
end
|
744
|
+
end
|
745
|
+
|
746
|
+
module Users
|
747
|
+
Authenticate = Micro::Cases.flow([
|
748
|
+
FindByEmail,
|
749
|
+
ValidatePassword
|
750
|
+
])
|
751
|
+
end
|
752
|
+
|
753
|
+
Users::Authenticate
|
754
|
+
.call(email: 'somebody@test.com', password: 'password')
|
755
|
+
.on_success { |result| sign_in(result[:user]) }
|
756
|
+
.on_failure(:wrong_password) { render status: 401 }
|
757
|
+
.on_failure(:user_not_found) { render status: 404 }
|
758
|
+
```
|
759
|
+
|
760
|
+
First, lets see the attributes used by each use case:
|
761
|
+
|
762
|
+
```ruby
|
763
|
+
class Users::FindByEmail < Micro::Case
|
764
|
+
attribute :email
|
765
|
+
end
|
766
|
+
|
767
|
+
class Users::ValidatePassword < Micro::Case
|
768
|
+
attributes :user, :password
|
769
|
+
end
|
770
|
+
```
|
771
|
+
|
772
|
+
As you can see the `Users::ValidatePassword` expects a user as its input. So, how does it receives the user?
|
773
|
+
It receives the user from the `Users::FindByEmail` success result!
|
646
774
|
|
647
|
-
|
775
|
+
And this, is the power of use cases composition because the output
|
776
|
+
of one step will compose the input of the next use case in the flow!
|
777
|
+
|
778
|
+
> input **>>** process **>>** output
|
779
|
+
|
780
|
+
> **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
781
|
|
649
782
|
[⬆️ Back to Top](#table-of-contents-)
|
650
783
|
|
784
|
+
#### How to understand what is happening during a flow execution?
|
785
|
+
|
786
|
+
Use `Micro::Case::Result#transitions`!
|
787
|
+
|
788
|
+
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.
|
789
|
+
|
790
|
+
```ruby
|
791
|
+
user_authenticated =
|
792
|
+
Users::Authenticate.call(email: 'rodrigo@test.com', password: user_password)
|
793
|
+
|
794
|
+
user_authenticated.transitions
|
795
|
+
[
|
796
|
+
{
|
797
|
+
:use_case => {
|
798
|
+
:class => Users::FindByEmail,
|
799
|
+
:attributes => { :email => "rodrigo@test.com" }
|
800
|
+
},
|
801
|
+
:success => {
|
802
|
+
:type => :ok,
|
803
|
+
:result => {
|
804
|
+
:user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
|
805
|
+
}
|
806
|
+
},
|
807
|
+
:accessible_attributes => [ :email, :password ]
|
808
|
+
},
|
809
|
+
{
|
810
|
+
:use_case => {
|
811
|
+
:class => Users::ValidatePassword,
|
812
|
+
:attributes => {
|
813
|
+
:user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
|
814
|
+
:password => "123456"
|
815
|
+
}
|
816
|
+
},
|
817
|
+
:success => {
|
818
|
+
:type => :ok,
|
819
|
+
:result => {
|
820
|
+
:user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
|
821
|
+
}
|
822
|
+
},
|
823
|
+
:accessible_attributes => [ :email, :password, :user ]
|
824
|
+
}
|
825
|
+
]
|
826
|
+
```
|
827
|
+
|
828
|
+
The example above shows the output generated by the `Micro::Case::Result#transitions`.
|
829
|
+
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.
|
830
|
+
|
831
|
+
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).
|
832
|
+
|
833
|
+
> **Note:** The [`Micro::Case::Result#then`](#how-to-use-the-microcaseresultthen-method) increments the `Micro::Case::Result#transitions`.
|
834
|
+
|
835
|
+
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.
|
836
|
+
|
837
|
+
##### `Micro::Case::Result#transitions` schema
|
838
|
+
```ruby
|
839
|
+
[
|
840
|
+
{
|
841
|
+
use_case: {
|
842
|
+
class: <Micro::Case>,# Use case which was executed
|
843
|
+
attributes: <Hash> # (Input) The use case's attributes
|
844
|
+
},
|
845
|
+
[success:, failure:] => { # (Output)
|
846
|
+
type: <Symbol>, # Result type. Defaults:
|
847
|
+
# Success = :ok, Failure = :error/:exception
|
848
|
+
result: <Hash> # The data returned by the use case
|
849
|
+
},
|
850
|
+
accessible_attributes: <Array>, # Properties that can be accessed by the use case's attributes,
|
851
|
+
# starting with Hash used to invoke it and which are incremented
|
852
|
+
# with each result value of the flow's use cases.
|
853
|
+
}
|
854
|
+
]
|
855
|
+
```
|
856
|
+
|
651
857
|
#### Is it possible to declare a flow which includes the use case itself?
|
652
858
|
|
653
859
|
Answer: Yes, it is! You can use the `self.call!` macro. e.g:
|
@@ -657,7 +863,7 @@ class ConvertTextToNumber < Micro::Case
|
|
657
863
|
attribute :text
|
658
864
|
|
659
865
|
def call!
|
660
|
-
Success
|
866
|
+
Success result: { number: text.to_i }
|
661
867
|
end
|
662
868
|
end
|
663
869
|
|
@@ -665,7 +871,7 @@ class ConvertNumberToText < Micro::Case
|
|
665
871
|
attribute :number
|
666
872
|
|
667
873
|
def call!
|
668
|
-
Success
|
874
|
+
Success result: { text: number.to_s }
|
669
875
|
end
|
670
876
|
end
|
671
877
|
|
@@ -677,17 +883,17 @@ class Double < Micro::Case
|
|
677
883
|
attribute :number
|
678
884
|
|
679
885
|
def call!
|
680
|
-
Success
|
886
|
+
Success result: { number: number * 2 }
|
681
887
|
end
|
682
888
|
end
|
683
889
|
|
684
890
|
result = Double.call(text: '4')
|
685
891
|
|
686
892
|
result.success? # true
|
687
|
-
result
|
893
|
+
result[:number] # "8"
|
688
894
|
|
689
895
|
# NOTE: This feature can be used with the Micro::Case::Safe.
|
690
|
-
# Checkout
|
896
|
+
# Checkout this test: https://github.com/serradura/u-case/blob/714c6b658fc6aa02617e6833ddee09eddc760f2a/test/micro/case/safe/with_inner_flow_test.rb
|
691
897
|
```
|
692
898
|
|
693
899
|
[⬆️ Back to Top](#table-of-contents-)
|
@@ -701,7 +907,7 @@ class Double < Micro::Case::Strict
|
|
701
907
|
attribute :numbers
|
702
908
|
|
703
909
|
def call!
|
704
|
-
Success
|
910
|
+
Success result: { numbers: numbers.map { |number| number * 2 } }
|
705
911
|
end
|
706
912
|
end
|
707
913
|
|
@@ -715,7 +921,7 @@ Double.call({})
|
|
715
921
|
|
716
922
|
### `Micro::Case::Safe` - Is there some feature to auto handle exceptions inside of a use case or flow?
|
717
923
|
|
718
|
-
Answer: Yes, there is!
|
924
|
+
Answer: Yes, there is one!
|
719
925
|
|
720
926
|
**Use cases:**
|
721
927
|
|
@@ -730,14 +936,18 @@ class Divide < Micro::Case::Safe
|
|
730
936
|
attributes :a, :b
|
731
937
|
|
732
938
|
def call!
|
733
|
-
|
734
|
-
|
939
|
+
if a.is_a?(Integer) && b.is_a?(Integer)
|
940
|
+
Success result: { number: a / b}
|
941
|
+
else
|
942
|
+
Failure(:not_an_integer)
|
943
|
+
end
|
735
944
|
end
|
736
945
|
end
|
737
946
|
|
738
947
|
result = Divide.call(a: 2, b: 0)
|
739
|
-
result.type == :exception
|
740
|
-
result.
|
948
|
+
result.type == :exception # true
|
949
|
+
result.data # { exception: #<ZeroDivisionError...> }
|
950
|
+
result[:exception].is_a?(ZeroDivisionError) # true
|
741
951
|
|
742
952
|
result.on_failure(:exception) do |exception|
|
743
953
|
AppLogger.error(exception.message) # E, [2019-08-21T00:05:44.195506 #9532] ERROR -- : divided by 0
|
@@ -758,25 +968,18 @@ end
|
|
758
968
|
# Another note:
|
759
969
|
# ------------
|
760
970
|
# It is possible to rescue an exception even when is a safe use case.
|
761
|
-
# Examples: https://github.com/serradura/u-case/blob/
|
971
|
+
# Examples: https://github.com/serradura/u-case/blob/714c6b658fc6aa02617e6833ddee09eddc760f2a/test/micro/case/safe_test.rb#L90-L118
|
762
972
|
```
|
763
973
|
|
764
|
-
|
974
|
+
[⬆️ Back to Top](#table-of-contents-)
|
975
|
+
|
976
|
+
#### `Micro::Cases::Safe::Flow`
|
765
977
|
|
766
978
|
As the safe use cases, safe flows can intercept an exception in any of its steps. These are the ways to define one:
|
767
979
|
|
768
980
|
```ruby
|
769
981
|
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([
|
982
|
+
Create = Micro::Cases.safe_flow([
|
780
983
|
ProcessParams,
|
781
984
|
ValidateParams,
|
782
985
|
Persist,
|
@@ -794,33 +997,63 @@ module Users
|
|
794
997
|
SendToCRM
|
795
998
|
end
|
796
999
|
end
|
1000
|
+
```
|
797
1001
|
|
1002
|
+
[⬆️ Back to Top](#table-of-contents-)
|
798
1003
|
|
799
|
-
|
800
|
-
# ! Deprecated: Micro::Case::Safe::Flow mixin ! #
|
801
|
-
# !-------------------------------------------! #
|
1004
|
+
#### `Micro::Case::Result#on_exception`
|
802
1005
|
|
803
|
-
|
804
|
-
# Deprecation: Micro::Case::Flow mixin is being deprecated, please use `Micro::Case` inheritance instead.
|
1006
|
+
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
1007
|
|
806
|
-
|
807
|
-
|
808
|
-
|
1008
|
+
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.
|
1009
|
+
|
1010
|
+
> **Note**: this feature will work better if you use it with a `Micro::Case::Safe` use case/flow.
|
809
1011
|
|
810
|
-
|
1012
|
+
How does it work?
|
1013
|
+
|
1014
|
+
```ruby
|
1015
|
+
class Divide < Micro::Case::Safe
|
1016
|
+
attributes :a, :b
|
1017
|
+
|
1018
|
+
def call!
|
1019
|
+
Success result: { division: a / b }
|
811
1020
|
end
|
812
1021
|
end
|
813
1022
|
|
814
|
-
|
1023
|
+
Divide
|
1024
|
+
.call(a: 2, b: 0)
|
1025
|
+
.on_success { |result| puts result[:division] }
|
1026
|
+
.on_exception(TypeError) { puts 'Please, use only numeric attributes.' }
|
1027
|
+
.on_exception(ZeroDivisionError) { |_error| puts "Can't divide a number by 0." }
|
1028
|
+
.on_exception { |_error, _use_case| puts 'Oh no, something went wrong!' }
|
1029
|
+
|
1030
|
+
# Output:
|
1031
|
+
# -------
|
1032
|
+
# Can't divide a number by 0
|
1033
|
+
# Oh no, something went wrong!
|
1034
|
+
|
1035
|
+
Divide.
|
1036
|
+
.call(a: 2, b: '2').
|
1037
|
+
.on_success { |result| puts result[:division] }
|
1038
|
+
.on_exception(TypeError) { puts 'Please, use only numeric attributes.' }
|
1039
|
+
.on_exception(ZeroDivisionError) { |_error| puts "Can't divide a number by 0." }
|
1040
|
+
.on_exception { |_error, _use_case| puts 'Oh no, something went wrong!' }
|
1041
|
+
|
1042
|
+
# Output:
|
1043
|
+
# -------
|
1044
|
+
# Please, use only numeric attributes.
|
1045
|
+
# Oh no, something went wrong!
|
815
1046
|
```
|
816
1047
|
|
1048
|
+
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.
|
1049
|
+
|
817
1050
|
[⬆️ Back to Top](#table-of-contents-)
|
818
1051
|
|
819
|
-
### `u-case/
|
1052
|
+
### `u-case/with_activemodel_validation` - How to validate use case attributes?
|
820
1053
|
|
821
1054
|
**Requirement:**
|
822
1055
|
|
823
|
-
To do this your application must have the [activemodel >= 3.2](https://rubygems.org/gems/activemodel) as a dependency.
|
1056
|
+
To do this your application must have the [activemodel >= 3.2, < 6.1.0](https://rubygems.org/gems/activemodel) as a dependency.
|
824
1057
|
|
825
1058
|
```ruby
|
826
1059
|
#
|
@@ -833,9 +1066,9 @@ class Multiply < Micro::Case
|
|
833
1066
|
validates :a, :b, presence: true, numericality: true
|
834
1067
|
|
835
1068
|
def call!
|
836
|
-
return Failure
|
1069
|
+
return Failure :validation_error, result: { errors: self.errors } if invalid?
|
837
1070
|
|
838
|
-
Success
|
1071
|
+
Success result: { number: a * b }
|
839
1072
|
end
|
840
1073
|
end
|
841
1074
|
|
@@ -844,10 +1077,10 @@ end
|
|
844
1077
|
# your use cases on validation errors, you can use:
|
845
1078
|
|
846
1079
|
# In some file. e.g: A Rails initializer
|
847
|
-
require 'u-case/
|
1080
|
+
require 'u-case/with_activemodel_validation' # or require 'micro/case/with_validation'
|
848
1081
|
|
849
1082
|
# In the Gemfile
|
850
|
-
gem 'u-case', require: 'u-case/
|
1083
|
+
gem 'u-case', require: 'u-case/with_activemodel_validation'
|
851
1084
|
|
852
1085
|
# Using this approach, you can rewrite the previous example with less code. e.g:
|
853
1086
|
|
@@ -857,7 +1090,7 @@ class Multiply < Micro::Case
|
|
857
1090
|
validates :a, :b, presence: true, numericality: true
|
858
1091
|
|
859
1092
|
def call!
|
860
|
-
Success
|
1093
|
+
Success result: { number: a * b }
|
861
1094
|
end
|
862
1095
|
end
|
863
1096
|
|
@@ -869,10 +1102,10 @@ end
|
|
869
1102
|
|
870
1103
|
#### If I enabled the auto validation, is it possible to disable it only in specific use case classes?
|
871
1104
|
|
872
|
-
Answer: Yes, it is. To do this, you only need to use the `disable_auto_validation` macro. e.g:
|
1105
|
+
Answer: Yes, it is possible. To do this, you only need to use the `disable_auto_validation` macro. e.g:
|
873
1106
|
|
874
1107
|
```ruby
|
875
|
-
require 'u-case/
|
1108
|
+
require 'u-case/with_activemodel_validation'
|
876
1109
|
|
877
1110
|
class Multiply < Micro::Case
|
878
1111
|
disable_auto_validation
|
@@ -882,7 +1115,7 @@ class Multiply < Micro::Case
|
|
882
1115
|
validates :a, :b, presence: true, numericality: true
|
883
1116
|
|
884
1117
|
def call!
|
885
|
-
Success
|
1118
|
+
Success result: { number: a * b }
|
886
1119
|
end
|
887
1120
|
end
|
888
1121
|
|
@@ -894,168 +1127,190 @@ Multiply.call(a: 2, b: 'a')
|
|
894
1127
|
|
895
1128
|
[⬆️ Back to Top](#table-of-contents-)
|
896
1129
|
|
1130
|
+
#### `Kind::Validator`
|
1131
|
+
|
1132
|
+
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).
|
1133
|
+
|
1134
|
+
The example below shows how to validate the attributes data types.
|
1135
|
+
|
1136
|
+
```ruby
|
1137
|
+
class Todo::List::AddItem < Micro::Case
|
1138
|
+
attributes :user, :params
|
1139
|
+
|
1140
|
+
validates :user, kind: User
|
1141
|
+
validates :params, kind: ActionController::Parameters
|
1142
|
+
|
1143
|
+
def call!
|
1144
|
+
todo_params = params.require(:todo).permit(:title, :due_at)
|
1145
|
+
|
1146
|
+
todo = user.todos.create(todo_params)
|
1147
|
+
|
1148
|
+
Success result: { todo: todo }
|
1149
|
+
rescue ActionController::ParameterMissing => e
|
1150
|
+
Failure :parameter_missing, result: { message: e.message }
|
1151
|
+
end
|
1152
|
+
end
|
1153
|
+
```
|
1154
|
+
|
897
1155
|
## Benchmarks
|
898
1156
|
|
899
|
-
### `Micro::Case`
|
1157
|
+
### `Micro::Case` (v2.6.0)
|
900
1158
|
|
901
1159
|
#### Best overall
|
902
1160
|
|
903
1161
|
The table below contains the average between the [Success results](#success-results) and [Failure results](#failure-results) benchmarks.
|
904
1162
|
|
905
|
-
| Gem / Abstraction | Iterations per second | Comparison
|
906
|
-
| ---------------------- | --------------------: |
|
907
|
-
| **Micro::Case** |
|
908
|
-
| Dry::Monads |
|
909
|
-
| Interactor |
|
910
|
-
| Trailblazer::Operation |
|
911
|
-
| Dry::Transaction |
|
1163
|
+
| Gem / Abstraction | Iterations per second | Comparison |
|
1164
|
+
| ---------------------- | --------------------: | ----------------: |
|
1165
|
+
| **Micro::Case** | 105124.3 | _**The Fastest**_ |
|
1166
|
+
| Dry::Monads | 103290.1 | 0.02x slower |
|
1167
|
+
| Interactor | 21342.3 | 4.93x slower |
|
1168
|
+
| Trailblazer::Operation | 14652.7 | 7.17x slower |
|
1169
|
+
| Dry::Transaction | 5310.3 | 19.80x slower |
|
912
1170
|
|
913
1171
|
---
|
914
1172
|
|
915
1173
|
#### Success results
|
916
1174
|
|
917
|
-
| Gem / Abstraction | Iterations per second | Comparison
|
918
|
-
| ----------------- | --------------------: |
|
919
|
-
| Dry::Monads |
|
920
|
-
| **Micro::Case** |
|
921
|
-
| Interactor |
|
922
|
-
| Trailblazer::Operation |
|
923
|
-
| Dry::Transaction |
|
1175
|
+
| Gem / Abstraction | Iterations per second | Comparison |
|
1176
|
+
| ----------------- | --------------------: | ----------------: |
|
1177
|
+
| Dry::Monads | 134801.0 | _**The Fastest**_ |
|
1178
|
+
| **Micro::Case** | 105909.2 | 1.27x slower |
|
1179
|
+
| Interactor | 29458.2 | 4.58x slower |
|
1180
|
+
| Trailblazer::Operation | 14714.9 | 9.16x slower |
|
1181
|
+
| Dry::Transaction | 5642.6 | 28.89x slower |
|
924
1182
|
|
925
1183
|
<details>
|
926
1184
|
<summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
|
927
1185
|
|
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
|
-
```
|
1186
|
+
```ruby
|
1187
|
+
# Warming up --------------------------------------
|
1188
|
+
# Interactor 2.897k i/100ms
|
1189
|
+
# Trailblazer::Operation 1.494k i/100ms
|
1190
|
+
# Dry::Monads 13.854k i/100ms
|
1191
|
+
# Dry::Transaction 561.000 i/100ms
|
1192
|
+
# Micro::Case 10.523k i/100ms
|
1193
|
+
# Micro::Case::Strict 7.982k i/100ms
|
1194
|
+
# Micro::Case::Safe 10.568k i/100ms
|
1195
|
+
|
1196
|
+
# Calculating -------------------------------------
|
1197
|
+
# Interactor 29.458k (± 3.4%) i/s - 147.747k in 5.021405s
|
1198
|
+
# Trailblazer::Operation 14.715k (± 1.8%) i/s - 74.700k in 5.078128s
|
1199
|
+
# Dry::Monads 134.801k (± 8.7%) i/s - 678.846k in 5.088739s
|
1200
|
+
# Dry::Transaction 5.643k (± 2.1%) i/s - 28.611k in 5.072969s
|
1201
|
+
# Micro::Case 105.909k (± 2.4%) i/s - 536.673k in 5.070329s
|
1202
|
+
# Micro::Case::Strict 84.234k (± 1.5%) i/s - 423.046k in 5.023447s
|
1203
|
+
# Micro::Case::Safe 105.725k (± 1.9%) i/s - 538.968k in 5.099817s
|
1204
|
+
|
1205
|
+
# Comparison:
|
1206
|
+
# Dry::Monads: 134801.0 i/s
|
1207
|
+
# Micro::Case: 105909.2 i/s - 1.27x (± 0.00) slower
|
1208
|
+
# Micro::Case::Safe: 105725.0 i/s - 1.28x (± 0.00) slower
|
1209
|
+
# Micro::Case::Strict: 84234.4 i/s - 1.60x (± 0.00) slower
|
1210
|
+
# Interactor: 29458.2 i/s - 4.58x (± 0.00) slower
|
1211
|
+
# Trailblazer::Operation: 14714.9 i/s - 9.16x (± 0.00) slower
|
1212
|
+
# Dry::Transaction: 5642.6 i/s - 23.89x (± 0.00) slower
|
1213
|
+
```
|
957
1214
|
</details>
|
958
1215
|
|
959
1216
|
https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_success_result.rb
|
960
1217
|
|
961
1218
|
#### Failure results
|
962
1219
|
|
963
|
-
| Gem / Abstraction | Iterations per second | Comparison
|
964
|
-
| ----------------- | --------------------: |
|
965
|
-
| **Micro::Case** |
|
966
|
-
| Dry::Monads |
|
967
|
-
| Trailblazer::Operation |
|
968
|
-
| Interactor |
|
969
|
-
| Dry::Transaction |
|
1220
|
+
| Gem / Abstraction | Iterations per second | Comparison |
|
1221
|
+
| ----------------- | --------------------: | ----------------: |
|
1222
|
+
| **Micro::Case** | 104339.4 | _**The Fastest**_ |
|
1223
|
+
| Dry::Monads | 71779.2 | 1.45x slower |
|
1224
|
+
| Trailblazer::Operation | 14590.6 | 7.15x slower |
|
1225
|
+
| Interactor | 13226.5 | 7.89x slower |
|
1226
|
+
| Dry::Transaction | 4978.1 | 20.96x slower |
|
970
1227
|
|
971
1228
|
<details>
|
972
1229
|
<summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
|
973
1230
|
|
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
|
-
```
|
1231
|
+
```ruby
|
1232
|
+
# Warming up --------------------------------------
|
1233
|
+
# Interactor 1.339k i/100ms
|
1234
|
+
# Trailblazer::Operation 1.393k i/100ms
|
1235
|
+
# Dry::Monads 7.208k i/100ms
|
1236
|
+
# Dry::Transaction 423.000 i/100ms
|
1237
|
+
# Micro::Case 9.620k i/100ms
|
1238
|
+
# Micro::Case::Strict 8.238k i/100ms
|
1239
|
+
# Micro::Case::Safe 9.906k i/100ms
|
1240
|
+
|
1241
|
+
# Calculating -------------------------------------
|
1242
|
+
# Interactor 13.227k (± 3.3%) i/s - 66.950k in 5.067145s
|
1243
|
+
# Trailblazer::Operation 14.591k (± 4.0%) i/s - 73.829k in 5.069162s
|
1244
|
+
# Dry::Monads 71.779k (± 2.5%) i/s - 360.400k in 5.024294s
|
1245
|
+
# Dry::Transaction 4.978k (± 3.3%) i/s - 24.957k in 5.019153s
|
1246
|
+
# Micro::Case 103.957k (± 1.8%) i/s - 529.100k in 5.091221s
|
1247
|
+
# Micro::Case::Strict 83.094k (± 2.0%) i/s - 420.138k in 5.058233s
|
1248
|
+
# Micro::Case::Safe 104.339k (± 1.7%) i/s - 525.018k in 5.033381s
|
1249
|
+
|
1250
|
+
# Comparison:
|
1251
|
+
# Micro::Case::Safe: 104339.4 i/s
|
1252
|
+
# Micro::Case: 103957.2 i/s - same-ish: difference falls within error
|
1253
|
+
# Micro::Case::Strict: 83094.5 i/s - 1.26x (± 0.00) slower
|
1254
|
+
# Dry::Monads: 71779.2 i/s - 1.45x (± 0.00) slower
|
1255
|
+
# Trailblazer::Operation: 14590.6 i/s - 7.15x (± 0.00) slower
|
1256
|
+
# Interactor: 13226.5 i/s - 7.89x (± 0.00) slower
|
1257
|
+
# Dry::Transaction: 4978.1 i/s - 20.96x (± 0.00) slower
|
1258
|
+
```
|
1003
1259
|
</details>
|
1004
1260
|
|
1005
1261
|
https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_failure_result.rb
|
1006
1262
|
|
1007
1263
|
---
|
1008
1264
|
|
1009
|
-
### `Micro::Case::Flow`
|
1265
|
+
### `Micro::Case::Flow` (v2.6.0)
|
1010
1266
|
|
1011
1267
|
| 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.
|
1268
|
+
| ------------------ | ----------------: | ----------------: |
|
1269
|
+
| Micro::Case::Flow | _**The Fastest**_ | _**The Fastest**_ |
|
1270
|
+
| Micro::Case::Safe::Flow | 0x slower | 0x slower |
|
1271
|
+
| Interactor::Organizer | 1.27x slower | 5.48x slower |
|
1016
1272
|
|
1017
1273
|
\* The `Dry::Monads`, `Dry::Transaction`, `Trailblazer::Operation` are out of this analysis because all of them doesn't have this kind of feature.
|
1018
1274
|
|
1019
1275
|
<details>
|
1020
1276
|
<summary><strong>Success results</strong> - Show the full benchmark/ips results.</summary>
|
1021
1277
|
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
```
|
1278
|
+
```ruby
|
1279
|
+
# Warming up --------------------------------------
|
1280
|
+
# Interactor::Organizer 4.765k i/100ms
|
1281
|
+
# Micro::Case::Flow 5.372k i/100ms
|
1282
|
+
# Micro::Case::Safe::Flow 5.855k i/100ms
|
1283
|
+
# Calculating -------------------------------------
|
1284
|
+
# Interactor::Organizer 48.598k (± 5.2%) i/s - 243.015k in 5.014307s
|
1285
|
+
# Micro::Case::Flow 61.606k (± 4.4%) i/s - 311.576k in 5.068602s
|
1286
|
+
# Micro::Case::Safe::Flow 60.688k (± 4.8%) i/s - 304.460k in 5.028877s
|
1287
|
+
|
1288
|
+
# Comparison:
|
1289
|
+
# Micro::Case::Flow: 61606.3 i/s
|
1290
|
+
# Micro::Case::Safe::Flow: 60688.3 i/s - same-ish: difference falls within error
|
1291
|
+
# Interactor::Organizer: 48598.2 i/s - 1.27x slower\
|
1292
|
+
```
|
1038
1293
|
</details>
|
1039
1294
|
|
1040
1295
|
<details>
|
1041
1296
|
<summary><strong>Failure results</strong> - Show the full benchmark/ips results.</summary>
|
1042
1297
|
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1298
|
+
```ruby
|
1299
|
+
# Warming up --------------------------------------
|
1300
|
+
# Interactor::Organizer 2.209k i/100ms
|
1301
|
+
# Micro::Case::Flow 11.508k i/100ms
|
1302
|
+
# Micro::Case::Safe::Flow 11.605k i/100ms
|
1303
|
+
|
1304
|
+
# Calculating -------------------------------------
|
1305
|
+
# Interactor::Organizer 22.592k (± 2.8%) i/s - 114.868k in 5.088685s
|
1306
|
+
# Micro::Case::Flow 123.629k (± 2.9%) i/s - 621.432k in 5.030844s
|
1307
|
+
# Micro::Case::Safe::Flow 123.862k (± 3.0%) i/s - 626.670k in 5.064097s
|
1308
|
+
|
1309
|
+
# Comparison:
|
1310
|
+
# Micro::Case::Safe::Flow: 123862.4 i/s
|
1311
|
+
# Micro::Case::Flow: 123629.3 i/s - same-ish: difference falls within error
|
1312
|
+
# Interactor::Organizer: 22592.2 i/s - 5.48x slower
|
1313
|
+
```
|
1059
1314
|
</details>
|
1060
1315
|
|
1061
1316
|
https://github.com/serradura/u-case/tree/master/benchmarks/flow
|