u-case 2.3.1 → 3.0.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.sh +3 -2
- data/Gemfile +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
|
+
![Ruby](https://img.shields.io/badge/ruby-2.2+-ruby.svg?colorA=99004d&colorB=cc0066)
|
1
2
|
[![Gem](https://img.shields.io/gem/v/u-case.svg?style=flat-square)](https://rubygems.org/gems/u-case)
|
2
3
|
[![Build Status](https://travis-ci.com/serradura/u-case.svg?branch=master)](https://travis-ci.com/serradura/u-case)
|
3
4
|
[![Maintainability](https://api.codeclimate.com/v1/badges/5c3c8ad1b0b943f88efd/maintainability)](https://codeclimate.com/github/serradura/u-case/maintainability)
|
4
5
|
[![Test Coverage](https://api.codeclimate.com/v1/badges/5c3c8ad1b0b943f88efd/test_coverage)](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
|