u-case 2.1.1 → 2.5.0
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/.tool-versions +1 -1
- data/.travis.sh +5 -0
- data/.travis.yml +1 -0
- data/Gemfile +14 -1
- data/README.md +369 -83
- data/lib/micro/case.rb +93 -26
- data/lib/micro/case/error.rb +25 -9
- data/lib/micro/case/flow.rb +1 -1
- data/lib/micro/case/flow/reducer.rb +45 -16
- data/lib/micro/case/result.rb +89 -3
- data/lib/micro/case/safe.rb +1 -1
- data/lib/micro/case/safe/flow.rb +3 -3
- 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} +3 -3
- 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 +22 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 349609a8d927ab7fd8edb1eb96141dff2f78f9957f31f2b88a8975239fc0de87
|
4
|
+
data.tar.gz: 4b6293023f49f28dd8283656a0d423f7d24abbfb7f010a90ba4d0dd17f577d22
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0553e560e40f71d0e8284ebbbbf7a51326d8c9bc9de6ad0de76db526466a94f633e2b038563868a2c93234800b7bdacc30a99ba57f1624ce08463fdc012d165d
|
7
|
+
data.tar.gz: 2866ca201676827b2ffc82877b1615e7ecbb6fc6e993df17007c6446b348a850266faf162a0953954ee6adfb0d35a88c303bee385d2fb52a9c228f45513655db
|
data/.tool-versions
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby 2.6.
|
1
|
+
ruby 2.6.5
|
data/.travis.sh
CHANGED
@@ -11,3 +11,8 @@ if [[ ! $ruby_v =~ '2.2.0' ]]; then
|
|
11
11
|
ACTIVEMODEL_VERSION='5.2' bundle update
|
12
12
|
ACTIVEMODEL_VERSION='5.2' bundle exec rake test
|
13
13
|
fi
|
14
|
+
|
15
|
+
if [[ $ruby_v =~ '2.5.' ]] || [[ $ruby_v =~ '2.6.' ]] || [[ $ruby_v =~ '2.7.' ]]; then
|
16
|
+
ACTIVEMODEL_VERSION='6.0' bundle update
|
17
|
+
ACTIVEMODEL_VERSION='6.0' bundle exec rake test
|
18
|
+
fi
|
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,19 @@ 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
|
+
group :development, :test do
|
31
|
+
gem 'awesome_print', '~> 1.8'
|
32
|
+
|
33
|
+
gem 'pry-byebug', "~> #{pry_byebug_version}"
|
21
34
|
end
|
22
35
|
|
23
36
|
# Specify your gem's dependencies in u-case.gemspec
|
data/README.md
CHANGED
@@ -1,54 +1,66 @@
|
|
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.
|
16
17
|
5. Be fast and optimized (Check out the [benchmarks](#benchmarks) section).
|
17
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.
|
20
|
+
|
18
21
|
## Table of Contents <!-- omit in toc -->
|
19
|
-
- [
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
- [
|
24
|
-
|
25
|
-
- [
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
- [
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
- [Micro::Case](#
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
- [
|
51
|
-
- [
|
22
|
+
- [Required Ruby version](#required-ruby-version)
|
23
|
+
- [Dependencies](#dependencies)
|
24
|
+
- [Installation](#installation)
|
25
|
+
- [Usage](#usage)
|
26
|
+
- [`Micro::Case` - How to define a use case?](#microcase---how-to-define-a-use-case)
|
27
|
+
- [`Micro::Case::Result` - What is a use case result?](#microcaseresult---what-is-a-use-case-result)
|
28
|
+
- [What are the default result types?](#what-are-the-default-result-types)
|
29
|
+
- [How to define custom result types?](#how-to-define-custom-result-types)
|
30
|
+
- [Is it possible to define a custom result type without a block?](#is-it-possible-to-define-a-custom-result-type-without-a-block)
|
31
|
+
- [How to use the result hooks?](#how-to-use-the-result-hooks)
|
32
|
+
- [Why the failure hook (without a type) exposes a different kind of data?](#why-the-failure-hook-without-a-type-exposes-a-different-kind-of-data)
|
33
|
+
- [What happens if a result hook was declared multiple times?](#what-happens-if-a-result-hook-was-declared-multiple-times)
|
34
|
+
- [How to use the `Micro::Case::Result#then` method?](#how-to-use-the-microcaseresultthen-method)
|
35
|
+
- [`Micro::Case::Flow` - How to compose use cases?](#microcaseflow---how-to-compose-use-cases)
|
36
|
+
- [Is it possible to compose a use case flow with other ones?](#is-it-possible-to-compose-a-use-case-flow-with-other-ones)
|
37
|
+
- [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)
|
38
|
+
- [How to understand what is happening during a flow execution?](#how-to-understand-what-is-happening-during-a-flow-execution)
|
39
|
+
- [`Micro::Case::Result#transitions` schema](#microcaseresulttransitions-schema)
|
40
|
+
- [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)
|
41
|
+
- [`Micro::Case::Strict` - What is a strict use case?](#microcasestrict---what-is-a-strict-use-case)
|
42
|
+
- [`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)
|
43
|
+
- [`Micro::Case::Safe::Flow`](#microcasesafeflow)
|
44
|
+
- [`Micro::Case::Result#on_exception`](#microcaseresulton_exception)
|
45
|
+
- [`u-case/with_activemodel_validation` - How to validate use case attributes?](#u-casewith_activemodel_validation---how-to-validate-use-case-attributes)
|
46
|
+
- [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)
|
47
|
+
- [Kind::Validator](#kindvalidator)
|
48
|
+
- [Benchmarks](#benchmarks)
|
49
|
+
- [`Micro::Case`](#microcase)
|
50
|
+
- [Best overall](#best-overall)
|
51
|
+
- [Success results](#success-results)
|
52
|
+
- [Failure results](#failure-results)
|
53
|
+
- [`Micro::Case::Flow`](#microcaseflow)
|
54
|
+
- [Comparisons](#comparisons)
|
55
|
+
- [Examples](#examples)
|
56
|
+
- [1️⃣ Rails App (API)](#1️⃣-rails-app-api)
|
57
|
+
- [2️⃣ CLI calculator](#2️⃣-cli-calculator)
|
58
|
+
- [3️⃣ Users creation](#3️⃣-users-creation)
|
59
|
+
- [4️⃣ Rescuing exception inside of the use cases](#4️⃣-rescuing-exception-inside-of-the-use-cases)
|
60
|
+
- [Development](#development)
|
61
|
+
- [Contributing](#contributing)
|
62
|
+
- [License](#license)
|
63
|
+
- [Code of Conduct](#code-of-conduct)
|
52
64
|
|
53
65
|
## Required Ruby version
|
54
66
|
|
@@ -56,8 +68,15 @@ The main project goals are:
|
|
56
68
|
|
57
69
|
## Dependencies
|
58
70
|
|
59
|
-
|
60
|
-
|
71
|
+
1. [`kind`](https://github.com/serradura/kind) gem.
|
72
|
+
|
73
|
+
A simple type system (at runtime) for Ruby.
|
74
|
+
|
75
|
+
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.
|
76
|
+
2. [`u-attributes`](https://github.com/serradura/u-attributes) gem.
|
77
|
+
|
78
|
+
This gem allows defining read-only attributes, that is, your objects will have only getters to access their attributes data.
|
79
|
+
It is used to define the use case attributes.
|
61
80
|
|
62
81
|
## Installation
|
63
82
|
|
@@ -139,6 +158,7 @@ A `Micro::Case::Result` stores the use cases output data. These are their main m
|
|
139
158
|
- `#type` a Symbol which gives meaning for the result, this is useful to declare different types of failures or success.
|
140
159
|
- `#on_success` or `#on_failure` are hook methods that help you define the application flow.
|
141
160
|
- `#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).
|
161
|
+
- `#then` allows if the current result is a success, the `then` method will allow to applying a new use case for its value.
|
142
162
|
|
143
163
|
[⬆️ Back to Top](#table-of-contents-)
|
144
164
|
|
@@ -409,6 +429,48 @@ accum # 24
|
|
409
429
|
result.value * 4 == accum # true
|
410
430
|
```
|
411
431
|
|
432
|
+
#### How to use the `Micro::Case::Result#then` method?
|
433
|
+
|
434
|
+
```ruby
|
435
|
+
class ForbidNegativeNumber < Micro::Case
|
436
|
+
attribute :number
|
437
|
+
|
438
|
+
def call!
|
439
|
+
return Success { attributes } if number >= 0
|
440
|
+
|
441
|
+
Failure { attributes }
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
class Add3 < Micro::Case
|
446
|
+
attribute :number
|
447
|
+
|
448
|
+
def call!
|
449
|
+
Success { { number: number + 3 } }
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
result1 =
|
454
|
+
ForbidNegativeNumber
|
455
|
+
.call(number: -1)
|
456
|
+
.then(Add3)
|
457
|
+
|
458
|
+
result1.type # :error
|
459
|
+
result1.value # {'number' => -1}
|
460
|
+
result1.failure? # true
|
461
|
+
|
462
|
+
# ---
|
463
|
+
|
464
|
+
result2 =
|
465
|
+
ForbidNegativeNumber
|
466
|
+
.call(number: 1)
|
467
|
+
.then(Add3)
|
468
|
+
|
469
|
+
result2.type # :ok
|
470
|
+
result2.value # {'number' => 4}
|
471
|
+
result2.success? # true
|
472
|
+
```
|
473
|
+
|
412
474
|
[⬆️ Back to Top](#table-of-contents-)
|
413
475
|
|
414
476
|
### `Micro::Case::Flow` - How to compose use cases?
|
@@ -592,13 +654,150 @@ Note: You can blend any of the [available syntaxes/approaches](#how-to-create-a-
|
|
592
654
|
|
593
655
|
[⬆️ Back to Top](#table-of-contents-)
|
594
656
|
|
595
|
-
#### Is it possible a flow accumulates its input and merges each success result to use as the argument of
|
657
|
+
#### Is it possible a flow accumulates its input and merges each success result to use as the argument of the next use cases?
|
658
|
+
|
659
|
+
Answer: Yes, it is! Look at the example below to understand how the data accumulation works inside of the flow execution.
|
660
|
+
|
661
|
+
```ruby
|
662
|
+
module Users
|
663
|
+
class Find < Micro::Case
|
664
|
+
attribute :email
|
665
|
+
|
666
|
+
def call!
|
667
|
+
user = User.find_by(email: email)
|
668
|
+
|
669
|
+
return Success { { user: user } } if user
|
670
|
+
|
671
|
+
Failure(:user_not_found)
|
672
|
+
end
|
673
|
+
end
|
674
|
+
end
|
675
|
+
|
676
|
+
module Users
|
677
|
+
class ValidatePassword < Micro::Case::Strict
|
678
|
+
attributes :user, :password
|
679
|
+
|
680
|
+
def call!
|
681
|
+
return Failure(:user_must_be_persisted) if user.new_record?
|
682
|
+
return Failure(:wrong_password) if user.wrong_password?(password)
|
683
|
+
|
684
|
+
return Success { attributes(:user) }
|
685
|
+
end
|
686
|
+
end
|
687
|
+
end
|
688
|
+
|
689
|
+
module Users
|
690
|
+
Authenticate = Micro::Case::Flow([
|
691
|
+
Find,
|
692
|
+
ValidatePassword
|
693
|
+
])
|
694
|
+
end
|
695
|
+
|
696
|
+
Users::Authenticate
|
697
|
+
.call(email: 'somebody@test.com', password: 'password')
|
698
|
+
.on_success { |result| sign_in(result[:user]) }
|
699
|
+
.on_failure(:wrong_password) { |result| render status: 401 }
|
700
|
+
.on_failure(:user_not_found) { |result| render status: 404 }
|
701
|
+
```
|
702
|
+
|
703
|
+
First, lets see the attribute of each use case:
|
704
|
+
|
705
|
+
```ruby
|
706
|
+
class Users::Find < Micro::Case
|
707
|
+
attribute :email
|
708
|
+
end
|
709
|
+
|
710
|
+
class Users::ValidatePassword < Micro::Case
|
711
|
+
attributes :user, :password
|
712
|
+
end
|
713
|
+
```
|
714
|
+
|
715
|
+
As you can see the `Users::ValidatePassword` expects a user as its input. So, how does it receives the user?
|
716
|
+
It receives the user from the `Users::Find` success result!
|
717
|
+
|
718
|
+
And this, is the power of use cases composition because the output
|
719
|
+
of one flow will compose the input of the next use case in the flow!
|
720
|
+
|
721
|
+
> input **>>** process **>>** output
|
596
722
|
|
597
|
-
|
723
|
+
> **Note:** Check out these test examples [Micro::Case::Flow](https://github.com/serradura/u-case/blob/b6d63b0db0caada67d2a6cf5cc5937000c0acf04/test/micro/case/flow/reducer_test.rb) and [Micro::Case::Safe::Flow](https://github.com/serradura/u-case/blob/b1d84b355f2b92d329e10d5d56d8012df1d32681/test/micro/case/safe/flow/reducer_test.rb) to see different use cases sharing their own data.
|
598
724
|
|
599
725
|
[⬆️ Back to Top](#table-of-contents-)
|
600
726
|
|
601
|
-
####
|
727
|
+
#### How to understand what is happening during a flow execution?
|
728
|
+
|
729
|
+
Use `Micro::Case::Result#transitions`!
|
730
|
+
|
731
|
+
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.
|
732
|
+
|
733
|
+
```ruby
|
734
|
+
user_authenticated =
|
735
|
+
Users::Authenticate.call(email: 'rodrigo@test.com', password: user_password)
|
736
|
+
|
737
|
+
user_authenticated.transitions
|
738
|
+
[
|
739
|
+
{
|
740
|
+
:use_case => {
|
741
|
+
:class => Users::Find,
|
742
|
+
:attributes => { :email => "rodrigo@test.com" }
|
743
|
+
},
|
744
|
+
:success => {
|
745
|
+
:type => :ok,
|
746
|
+
:value => {
|
747
|
+
:user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
|
748
|
+
}
|
749
|
+
},
|
750
|
+
:accessible_attributes => [ :email, :password ]
|
751
|
+
},
|
752
|
+
{
|
753
|
+
:use_case => {
|
754
|
+
:class => Users::ValidatePassword,
|
755
|
+
:attributes => {
|
756
|
+
:user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
|
757
|
+
:password => "123456"
|
758
|
+
}
|
759
|
+
},
|
760
|
+
:success => {
|
761
|
+
:type => :ok,
|
762
|
+
:value => {
|
763
|
+
:user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
|
764
|
+
}
|
765
|
+
},
|
766
|
+
:accessible_attributes => [ :email, :password, :user ]
|
767
|
+
}
|
768
|
+
]
|
769
|
+
```
|
770
|
+
|
771
|
+
The example above shows the output generated by the `Micro::Case::Result#transitions`.
|
772
|
+
With it is possible to analyze the use cases execution order and what were the given `inputs` (attributes) and `outputs` (`success.value`) in the entire execution.
|
773
|
+
|
774
|
+
And look up the `accessible_attributes` property, because 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 [flow data accumulation](#is-it-possible-a-flow-accumulates-its-input-and-merges-each-success-result-to-use-as-the-argument-of-the-next-use-cases).
|
775
|
+
|
776
|
+
> **Note:** The [`Micro::Case::Result#then`](#how-to-use-the-microcaseresultthen-method) increments the `Micro::Case::Result#transitions`.
|
777
|
+
|
778
|
+
PS: Use the `Micro::Case::Result.disable_transition_tracking` global feature toggle to disable this feature (use once) and increase the use cases' performance.
|
779
|
+
|
780
|
+
##### `Micro::Case::Result#transitions` schema
|
781
|
+
```ruby
|
782
|
+
[
|
783
|
+
{
|
784
|
+
use_case: {
|
785
|
+
class: <Micro::Case>,# Use case which was executed
|
786
|
+
attributes: <Hash> # (Input) The use case's attributes
|
787
|
+
},
|
788
|
+
[success:, failure:] => { # (Output)
|
789
|
+
type: <Symbol>, # Result type. Defaults:
|
790
|
+
# Success = :ok, Failure = :error/:exception
|
791
|
+
value: <Hash> # The data returned by the use case
|
792
|
+
},
|
793
|
+
accessible_attributes: <Array>, # Properties that can be accessed by the use case's attributes,
|
794
|
+
# starting with Hash used to invoke it and which are incremented
|
795
|
+
# with each result value of the flow's use cases.
|
796
|
+
}
|
797
|
+
]
|
798
|
+
```
|
799
|
+
|
800
|
+
#### Is it possible to declare a flow which includes the use case itself?
|
602
801
|
|
603
802
|
Answer: Yes, it is! You can use the `self.call!` macro. e.g:
|
604
803
|
|
@@ -620,16 +819,15 @@ class ConvertNumberToText < Micro::Case
|
|
620
819
|
end
|
621
820
|
|
622
821
|
class Double < Micro::Case
|
822
|
+
flow ConvertTextToNumber,
|
823
|
+
self.call!,
|
824
|
+
ConvertNumberToText
|
825
|
+
|
623
826
|
attribute :number
|
624
827
|
|
625
828
|
def call!
|
626
829
|
Success { { number: number * 2 } }
|
627
830
|
end
|
628
|
-
|
629
|
-
# NOTE: You need to declare the flow after the definition of the attributes.
|
630
|
-
flow ConvertTextToNumber,
|
631
|
-
self.call!,
|
632
|
-
ConvertNumberToText
|
633
831
|
end
|
634
832
|
|
635
833
|
result = Double.call(text: '4')
|
@@ -712,7 +910,9 @@ end
|
|
712
910
|
# Examples: https://github.com/serradura/u-case/blob/5a85fc238b63811a32737493dc6c59965f92491d/test/micro/case/safe_test.rb#L95-L123
|
713
911
|
```
|
714
912
|
|
715
|
-
|
913
|
+
[⬆️ Back to Top](#table-of-contents-)
|
914
|
+
|
915
|
+
#### `Micro::Case::Safe::Flow`
|
716
916
|
|
717
917
|
As the safe use cases, safe flows can intercept an exception in any of its steps. These are the ways to define one:
|
718
918
|
|
@@ -746,7 +946,6 @@ module Users
|
|
746
946
|
end
|
747
947
|
end
|
748
948
|
|
749
|
-
|
750
949
|
# !------------------------------------------ ! #
|
751
950
|
# ! Deprecated: Micro::Case::Safe::Flow mixin ! #
|
752
951
|
# !-------------------------------------------! #
|
@@ -767,11 +966,59 @@ end
|
|
767
966
|
|
768
967
|
[⬆️ Back to Top](#table-of-contents-)
|
769
968
|
|
770
|
-
|
969
|
+
#### `Micro::Case::Result#on_exception`
|
970
|
+
|
971
|
+
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.
|
972
|
+
|
973
|
+
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.
|
974
|
+
|
975
|
+
> **Note** this feature will work better if you use it with a `Micro::Case::Safe` use case/flow.
|
976
|
+
|
977
|
+
How does it work?
|
978
|
+
|
979
|
+
```ruby
|
980
|
+
class Divide < Micro::Case::Safe
|
981
|
+
attributes :a, :b
|
982
|
+
|
983
|
+
def call!
|
984
|
+
Success(division: a / b)
|
985
|
+
end
|
986
|
+
end
|
987
|
+
|
988
|
+
Divide
|
989
|
+
.call(a: 2, b: 0)
|
990
|
+
.on_success { |result| puts result[:division] }
|
991
|
+
.on_exception(TypeError) { puts 'Please, use only numeric attributes.' }
|
992
|
+
.on_exception(ZeroDivisionError) { |_error| puts "Can't divide a number by 0." }
|
993
|
+
.on_exception { |_error, _use_case| puts 'Oh no, something went wrong!' }
|
994
|
+
|
995
|
+
# Output:
|
996
|
+
# -------
|
997
|
+
# Can't divide a number by 0
|
998
|
+
# Oh no, something went wrong!
|
999
|
+
|
1000
|
+
Divide.
|
1001
|
+
.call(a: 2, b: '2').
|
1002
|
+
.on_success { |result| puts result[:division] }
|
1003
|
+
.on_exception(TypeError) { puts 'Please, use only numeric attributes.' }
|
1004
|
+
.on_exception(ZeroDivisionError) { |_error| puts "Can't divide a number by 0." }
|
1005
|
+
.on_exception { |_error, _use_case| puts 'Oh no, something went wrong!' }
|
1006
|
+
|
1007
|
+
# Output:
|
1008
|
+
# -------
|
1009
|
+
# Please, use only numeric attributes.
|
1010
|
+
# Oh no, something went wrong!
|
1011
|
+
```
|
1012
|
+
|
1013
|
+
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.
|
1014
|
+
|
1015
|
+
[⬆️ Back to Top](#table-of-contents-)
|
1016
|
+
|
1017
|
+
### `u-case/with_activemodel_validation` - How to validate use case attributes?
|
771
1018
|
|
772
1019
|
**Requirement:**
|
773
1020
|
|
774
|
-
To do this your application must have the [activemodel >= 3.2](https://rubygems.org/gems/activemodel) as a dependency.
|
1021
|
+
To do this your application must have the [activemodel >= 3.2, < 6.1.0](https://rubygems.org/gems/activemodel) as a dependency.
|
775
1022
|
|
776
1023
|
```ruby
|
777
1024
|
#
|
@@ -795,10 +1042,10 @@ end
|
|
795
1042
|
# your use cases on validation errors, you can use:
|
796
1043
|
|
797
1044
|
# In some file. e.g: A Rails initializer
|
798
|
-
require 'u-case/
|
1045
|
+
require 'u-case/with_activemodel_validation' # or require 'micro/case/with_validation'
|
799
1046
|
|
800
1047
|
# In the Gemfile
|
801
|
-
gem 'u-case', require: 'u-case/
|
1048
|
+
gem 'u-case', require: 'u-case/with_activemodel_validation'
|
802
1049
|
|
803
1050
|
# Using this approach, you can rewrite the previous example with less code. e.g:
|
804
1051
|
|
@@ -823,7 +1070,7 @@ end
|
|
823
1070
|
Answer: Yes, it is. To do this, you only need to use the `disable_auto_validation` macro. e.g:
|
824
1071
|
|
825
1072
|
```ruby
|
826
|
-
require 'u-case/
|
1073
|
+
require 'u-case/with_activemodel_validation'
|
827
1074
|
|
828
1075
|
class Multiply < Micro::Case
|
829
1076
|
disable_auto_validation
|
@@ -845,6 +1092,31 @@ Multiply.call(a: 2, b: 'a')
|
|
845
1092
|
|
846
1093
|
[⬆️ Back to Top](#table-of-contents-)
|
847
1094
|
|
1095
|
+
#### Kind::Validator
|
1096
|
+
|
1097
|
+
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).
|
1098
|
+
|
1099
|
+
The example below shows how to validate the attributes data types.
|
1100
|
+
|
1101
|
+
```ruby
|
1102
|
+
class Todo::List::AddItem < Micro::Case
|
1103
|
+
attributes :user, :params
|
1104
|
+
|
1105
|
+
validates :user, kind: User
|
1106
|
+
validates :params, kind: ActionController::Parameters
|
1107
|
+
|
1108
|
+
def call!
|
1109
|
+
todo_params = Todo::Params.to_save(params)
|
1110
|
+
|
1111
|
+
todo = user.todos.create(todo_params)
|
1112
|
+
|
1113
|
+
Success { { todo: todo} }
|
1114
|
+
rescue ActionController::ParameterMissing => e
|
1115
|
+
Failure(:parameter_missing) { { message: e.message } }
|
1116
|
+
end
|
1117
|
+
end
|
1118
|
+
```
|
1119
|
+
|
848
1120
|
## Benchmarks
|
849
1121
|
|
850
1122
|
### `Micro::Case`
|
@@ -853,25 +1125,25 @@ Multiply.call(a: 2, b: 'a')
|
|
853
1125
|
|
854
1126
|
The table below contains the average between the [Success results](#success-results) and [Failure results](#failure-results) benchmarks.
|
855
1127
|
|
856
|
-
| Gem / Abstraction | Iterations per second | Comparison
|
857
|
-
| ---------------------- | --------------------: |
|
858
|
-
| **Micro::Case** | 116629.7 | _**The
|
859
|
-
| Dry::Monads | 101796.3 | 1.14x slower
|
860
|
-
| Interactor | 21230.5 | 5.49x slower
|
861
|
-
| Trailblazer::Operation | 16466.6 | 7.08x slower
|
862
|
-
| Dry::Transaction | 5069.5 | 23.00x slower
|
1128
|
+
| Gem / Abstraction | Iterations per second | Comparison |
|
1129
|
+
| ---------------------- | --------------------: | ----------------: |
|
1130
|
+
| **Micro::Case** | 116629.7 | _**The Fastest**_ |
|
1131
|
+
| Dry::Monads | 101796.3 | 1.14x slower |
|
1132
|
+
| Interactor | 21230.5 | 5.49x slower |
|
1133
|
+
| Trailblazer::Operation | 16466.6 | 7.08x slower |
|
1134
|
+
| Dry::Transaction | 5069.5 | 23.00x slower |
|
863
1135
|
|
864
1136
|
---
|
865
1137
|
|
866
1138
|
#### Success results
|
867
1139
|
|
868
|
-
| Gem / Abstraction | Iterations per second | Comparison
|
869
|
-
| ----------------- | --------------------: |
|
870
|
-
| Dry::Monads | 139352.5 | _**The
|
871
|
-
| **Micro::Case** | 124749.4 | 1.12x slower
|
872
|
-
| Interactor | 28974.4 | 4.81x slower
|
873
|
-
| Trailblazer::Operation | 17275.6 | 8.07x slower
|
874
|
-
| Dry::Transaction | 5571.7 | 25.01x slower
|
1140
|
+
| Gem / Abstraction | Iterations per second | Comparison |
|
1141
|
+
| ----------------- | --------------------: | ----------------: |
|
1142
|
+
| Dry::Monads | 139352.5 | _**The Fastest**_ |
|
1143
|
+
| **Micro::Case** | 124749.4 | 1.12x slower |
|
1144
|
+
| Interactor | 28974.4 | 4.81x slower |
|
1145
|
+
| Trailblazer::Operation | 17275.6 | 8.07x slower |
|
1146
|
+
| Dry::Transaction | 5571.7 | 25.01x slower |
|
875
1147
|
|
876
1148
|
<details>
|
877
1149
|
<summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
|
@@ -911,13 +1183,13 @@ https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_success
|
|
911
1183
|
|
912
1184
|
#### Failure results
|
913
1185
|
|
914
|
-
| Gem / Abstraction | Iterations per second | Comparison
|
915
|
-
| ----------------- | --------------------: |
|
916
|
-
| **Micro::Case** | 108510.0 | _**The
|
917
|
-
| Dry::Monads | 64240.1 | 1.69x slower
|
918
|
-
| Trailblazer::Operation | 15657.7 | 6.93x slower
|
919
|
-
| Interactor | 13486.7 | 8.05x slower
|
920
|
-
| Dry::Transaction | 4567.3 | 23.76x slower
|
1186
|
+
| Gem / Abstraction | Iterations per second | Comparison |
|
1187
|
+
| ----------------- | --------------------: | ----------------: |
|
1188
|
+
| **Micro::Case** | 108510.0 | _**The Fastest**_ |
|
1189
|
+
| Dry::Monads | 64240.1 | 1.69x slower |
|
1190
|
+
| Trailblazer::Operation | 15657.7 | 6.93x slower |
|
1191
|
+
| Interactor | 13486.7 | 8.05x slower |
|
1192
|
+
| Dry::Transaction | 4567.3 | 23.76x slower |
|
921
1193
|
|
922
1194
|
<details>
|
923
1195
|
<summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
|
@@ -960,10 +1232,10 @@ https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_failure
|
|
960
1232
|
### `Micro::Case::Flow`
|
961
1233
|
|
962
1234
|
| 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) |
|
963
|
-
| ------------------ |
|
964
|
-
| Micro::Case::Flow | _**The
|
965
|
-
| Micro::Case::Safe::Flow | 0x slower
|
966
|
-
| Interactor::Organizer | 1.47x slower
|
1235
|
+
| ------------------ | ----------------: | ----------------: |
|
1236
|
+
| Micro::Case::Flow | _**The Fastest**_ | _**The Fastest**_ |
|
1237
|
+
| Micro::Case::Safe::Flow | 0x slower | 0x slower |
|
1238
|
+
| Interactor::Organizer | 1.47x slower | 5.51x slower |
|
967
1239
|
|
968
1240
|
\* The `Dry::Monads`, `Dry::Transaction`, `Trailblazer::Operation` are out of this analysis because all of them doesn't have this kind of feature.
|
969
1241
|
|
@@ -1022,13 +1294,27 @@ Check it out implementations of the same use case with different gems/abstractio
|
|
1022
1294
|
|
1023
1295
|
## Examples
|
1024
1296
|
|
1025
|
-
1
|
1026
|
-
|
1297
|
+
### 1️⃣ Rails App (API)
|
1298
|
+
|
1299
|
+
> This project shows different kinds of architecture (one per commit), and in the last one, how to use the Micro::Case gem to handle the application business logic.
|
1300
|
+
>
|
1301
|
+
> Link: https://github.com/serradura/from-fat-controllers-to-use-cases
|
1302
|
+
|
1303
|
+
### 2️⃣ CLI calculator
|
1304
|
+
|
1305
|
+
> Rake tasks to demonstrate how to handle user data, and how to use different failure types to control the program flow.
|
1306
|
+
>
|
1307
|
+
> Link: https://github.com/serradura/u-case/tree/master/examples/calculator
|
1308
|
+
|
1309
|
+
### 3️⃣ Users creation
|
1310
|
+
|
1311
|
+
> An example of a use case flow that define steps to sanitize, validate, and persist its input data.
|
1312
|
+
>
|
1313
|
+
> Link: https://github.com/serradura/u-case/blob/master/examples/users_creation.rb
|
1027
1314
|
|
1028
|
-
|
1029
|
-
3. [CLI calculator](https://github.com/serradura/u-case/tree/master/examples/calculator)
|
1315
|
+
### 4️⃣ Rescuing exception inside of the use cases
|
1030
1316
|
|
1031
|
-
|
1317
|
+
> Link: https://github.com/serradura/u-case/blob/master/examples/rescuing_exceptions.rb
|
1032
1318
|
|
1033
1319
|
[⬆️ Back to Top](#table-of-contents-)
|
1034
1320
|
|