u-case 2.1.0 → 2.4.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 +314 -81
- data/lib/micro/case.rb +80 -25
- 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 +73 -1
- 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_activemodel_validation.rb +41 -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 +22 -6
- data/lib/micro/case/with_validation.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d5b58d2e19bca34b4337e348d4d498b21ee12134082f897122c957ab79e19fa7
|
4
|
+
data.tar.gz: 6cb9e76227b0dc96d92de0c7ae1abdfe4bce2f840f0dced7ce1cae24908c0efb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea7dd10cd004dc3ef7e3fbfe062afc6e7a09e172b9fca1c5d2f2300c6d876b19eeb4935d06c8650010b14ca318486226fe08ec5c9fa57a9570870caf9b470bb2
|
7
|
+
data.tar.gz: 5742702a5c64d2ed8a32cb83aaa6ee8fb5cb550b7144eba248cf058e340f47751528e38214991f1c92c50370b4a6240f7e29a963d872ffe78219d2d91fa5637e
|
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,64 @@
|
|
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
|
-
- [
|
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
|
+
- [`u-case/with_activemodel_validation` - How to validate use case attributes?](#u-casewith_activemodel_validation---how-to-validate-use-case-attributes)
|
44
|
+
- [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)
|
45
|
+
- [Kind::Validator](#kindvalidator)
|
46
|
+
- [Benchmarks](#benchmarks)
|
47
|
+
- [`Micro::Case`](#microcase)
|
48
|
+
- [Best overall](#best-overall)
|
49
|
+
- [Success results](#success-results)
|
50
|
+
- [Failure results](#failure-results)
|
51
|
+
- [`Micro::Case::Flow`](#microcaseflow)
|
52
|
+
- [Comparisons](#comparisons)
|
53
|
+
- [Examples](#examples)
|
54
|
+
- [1️⃣ Rails App (API)](#1️⃣-rails-app-api)
|
55
|
+
- [2️⃣ CLI calculator](#2️⃣-cli-calculator)
|
56
|
+
- [3️⃣ Users creation](#3️⃣-users-creation)
|
57
|
+
- [4️⃣ Rescuing exception inside of the use cases](#4️⃣-rescuing-exception-inside-of-the-use-cases)
|
58
|
+
- [Development](#development)
|
59
|
+
- [Contributing](#contributing)
|
60
|
+
- [License](#license)
|
61
|
+
- [Code of Conduct](#code-of-conduct)
|
52
62
|
|
53
63
|
## Required Ruby version
|
54
64
|
|
@@ -56,8 +66,15 @@ The main project goals are:
|
|
56
66
|
|
57
67
|
## Dependencies
|
58
68
|
|
59
|
-
|
60
|
-
|
69
|
+
1. [`kind`](https://github.com/serradura/kind) gem.
|
70
|
+
|
71
|
+
A simple type system (at runtime) for Ruby.
|
72
|
+
|
73
|
+
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.
|
74
|
+
2. [`u-attributes`](https://github.com/serradura/u-attributes) gem.
|
75
|
+
|
76
|
+
This gem allows defining read-only attributes, that is, your objects will have only getters to access their attributes data.
|
77
|
+
It is used to define the use case attributes.
|
61
78
|
|
62
79
|
## Installation
|
63
80
|
|
@@ -139,6 +156,7 @@ A `Micro::Case::Result` stores the use cases output data. These are their main m
|
|
139
156
|
- `#type` a Symbol which gives meaning for the result, this is useful to declare different types of failures or success.
|
140
157
|
- `#on_success` or `#on_failure` are hook methods that help you define the application flow.
|
141
158
|
- `#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).
|
159
|
+
- `#then` allows if the current result is a success, the `then` method will allow to applying a new use case for its value.
|
142
160
|
|
143
161
|
[⬆️ Back to Top](#table-of-contents-)
|
144
162
|
|
@@ -409,6 +427,48 @@ accum # 24
|
|
409
427
|
result.value * 4 == accum # true
|
410
428
|
```
|
411
429
|
|
430
|
+
#### How to use the `Micro::Case::Result#then` method?
|
431
|
+
|
432
|
+
```ruby
|
433
|
+
class ForbidNegativeNumber < Micro::Case
|
434
|
+
attribute :number
|
435
|
+
|
436
|
+
def call!
|
437
|
+
return Success { attributes } if number >= 0
|
438
|
+
|
439
|
+
Failure { attributes }
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
class Add3 < Micro::Case
|
444
|
+
attribute :number
|
445
|
+
|
446
|
+
def call!
|
447
|
+
Success { { number: number + 3 } }
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
result1 =
|
452
|
+
ForbidNegativeNumber
|
453
|
+
.call(number: -1)
|
454
|
+
.then(Add3)
|
455
|
+
|
456
|
+
result1.type # :error
|
457
|
+
result1.value # {'number' => -1}
|
458
|
+
result1.failure? # true
|
459
|
+
|
460
|
+
# ---
|
461
|
+
|
462
|
+
result2 =
|
463
|
+
ForbidNegativeNumber
|
464
|
+
.call(number: 1)
|
465
|
+
.then(Add3)
|
466
|
+
|
467
|
+
result2.type # :ok
|
468
|
+
result2.value # {'number' => 4}
|
469
|
+
result2.success? # true
|
470
|
+
```
|
471
|
+
|
412
472
|
[⬆️ Back to Top](#table-of-contents-)
|
413
473
|
|
414
474
|
### `Micro::Case::Flow` - How to compose use cases?
|
@@ -592,13 +652,148 @@ Note: You can blend any of the [available syntaxes/approaches](#how-to-create-a-
|
|
592
652
|
|
593
653
|
[⬆️ Back to Top](#table-of-contents-)
|
594
654
|
|
595
|
-
#### Is it possible a flow accumulates its input and merges each success result to use as the argument of
|
655
|
+
#### Is it possible a flow accumulates its input and merges each success result to use as the argument of the next use cases?
|
656
|
+
|
657
|
+
Answer: Yes, it is! Look at the example below to understand how the data accumulation works inside of the flow execution.
|
658
|
+
|
659
|
+
```ruby
|
660
|
+
module Users
|
661
|
+
class Find < Micro::Case
|
662
|
+
attribute :email
|
663
|
+
|
664
|
+
def call!
|
665
|
+
user = User.find_by(email: email)
|
666
|
+
|
667
|
+
return Success { { user: user } } if user
|
668
|
+
|
669
|
+
Failure(:user_not_found)
|
670
|
+
end
|
671
|
+
end
|
672
|
+
end
|
673
|
+
|
674
|
+
module Users
|
675
|
+
class ValidatePassword < Micro::Case::Strict
|
676
|
+
attributes :user, :password
|
677
|
+
|
678
|
+
def call!
|
679
|
+
return Failure(:user_must_be_persisted) if user.new_record?
|
680
|
+
return Failure(:wrong_password) if user.wrong_password?(password)
|
681
|
+
|
682
|
+
return Success { attributes(:user) }
|
683
|
+
end
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
module Users
|
688
|
+
Authenticate = Micro::Case::Flow([
|
689
|
+
Find,
|
690
|
+
ValidatePassword
|
691
|
+
])
|
692
|
+
end
|
693
|
+
|
694
|
+
Users::Authenticate
|
695
|
+
.call(email: 'somebody@test.com', password: 'password')
|
696
|
+
.on_success { |result| sign_in(result[:user]) }
|
697
|
+
.on_failure(:wrong_password) { |result| render status: 401 }
|
698
|
+
.on_failure(:user_not_found) { |result| render status: 404 }
|
699
|
+
```
|
700
|
+
|
701
|
+
First, lets see the attribute of each use case:
|
596
702
|
|
597
|
-
|
703
|
+
```ruby
|
704
|
+
class Users::Find < Micro::Case
|
705
|
+
attribute :email
|
706
|
+
end
|
707
|
+
|
708
|
+
class Users::ValidatePassword < Micro::Case
|
709
|
+
attributes :user, :password
|
710
|
+
end
|
711
|
+
```
|
712
|
+
|
713
|
+
As you can see the `Users::ValidatePassword` expects a user as its input. So, how it receives the user?
|
714
|
+
It receives the user from the `Users::Find` success result!
|
715
|
+
|
716
|
+
And this, is the power of use cases composition because the output
|
717
|
+
of one flow will compose the input of the next use case in the flow!
|
718
|
+
|
719
|
+
> input **>>** process **>>** output
|
720
|
+
|
721
|
+
> **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
722
|
|
599
723
|
[⬆️ Back to Top](#table-of-contents-)
|
600
724
|
|
601
|
-
####
|
725
|
+
#### How to understand what is happening during a flow execution?
|
726
|
+
|
727
|
+
Use `Micro::Case::Result#transitions`!
|
728
|
+
|
729
|
+
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.
|
730
|
+
|
731
|
+
```ruby
|
732
|
+
user_authenticated =
|
733
|
+
Users::Authenticate.call(email: 'rodrigo@test.com', password: user_password)
|
734
|
+
|
735
|
+
user_authenticated.transitions
|
736
|
+
[
|
737
|
+
{
|
738
|
+
:use_case => {
|
739
|
+
:class => Users::Find,
|
740
|
+
:attributes => { :email => "rodrigo@test.com" }
|
741
|
+
},
|
742
|
+
:success => {
|
743
|
+
:type => :ok,
|
744
|
+
:value => {
|
745
|
+
:user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
|
746
|
+
}
|
747
|
+
},
|
748
|
+
:accessible_attributes => [ :email, :password ]
|
749
|
+
},
|
750
|
+
{
|
751
|
+
:use_case => {
|
752
|
+
:class => Users::ValidatePassword,
|
753
|
+
:attributes => {
|
754
|
+
:user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
|
755
|
+
:password => "123456"
|
756
|
+
}
|
757
|
+
},
|
758
|
+
:success => {
|
759
|
+
:type => :ok,
|
760
|
+
:value => {
|
761
|
+
:user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
|
762
|
+
}
|
763
|
+
},
|
764
|
+
:accessible_attributes => [ :email, :password, :user ]
|
765
|
+
}
|
766
|
+
]
|
767
|
+
```
|
768
|
+
|
769
|
+
The example above shows the output generated by the `Micro::Case::Result#transitions`.
|
770
|
+
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.
|
771
|
+
|
772
|
+
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).
|
773
|
+
|
774
|
+
> **Note:** The [`Micro::Case::Result#then`](#how-to-use-the-microcaseresultthen-method) increments the `Micro::Case::Result#transitions`.
|
775
|
+
|
776
|
+
##### Micro::Case::Result#transitions schema.
|
777
|
+
```ruby
|
778
|
+
[
|
779
|
+
{
|
780
|
+
use_case: {
|
781
|
+
class: <Micro::Case>,# Use case which was executed
|
782
|
+
attributes: <Hash> # (Input) The use case's attributes
|
783
|
+
},
|
784
|
+
[success:, failure:] => { # (Output)
|
785
|
+
type: <Symbol>, # Result type. Defaults:
|
786
|
+
# Success = :ok, Failure = :error/:exception
|
787
|
+
value: <Hash> # The data returned by the use case
|
788
|
+
},
|
789
|
+
accessible_attributes: <Array>, # Properties that can be accessed by the use case's attributes,
|
790
|
+
# starting with Hash used to invoke it and which are incremented
|
791
|
+
# with each result value of the flow's use cases.
|
792
|
+
}
|
793
|
+
]
|
794
|
+
```
|
795
|
+
|
796
|
+
#### Is it possible to declare a flow which includes the use case itself?
|
602
797
|
|
603
798
|
Answer: Yes, it is! You can use the `self.call!` macro. e.g:
|
604
799
|
|
@@ -620,16 +815,15 @@ class ConvertNumberToText < Micro::Case
|
|
620
815
|
end
|
621
816
|
|
622
817
|
class Double < Micro::Case
|
818
|
+
flow ConvertTextToNumber,
|
819
|
+
self.call!,
|
820
|
+
ConvertNumberToText
|
821
|
+
|
623
822
|
attribute :number
|
624
823
|
|
625
824
|
def call!
|
626
825
|
Success { { number: number * 2 } }
|
627
826
|
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
827
|
end
|
634
828
|
|
635
829
|
result = Double.call(text: '4')
|
@@ -767,11 +961,11 @@ end
|
|
767
961
|
|
768
962
|
[⬆️ Back to Top](#table-of-contents-)
|
769
963
|
|
770
|
-
### `u-case/
|
964
|
+
### `u-case/with_activemodel_validation` - How to validate use case attributes?
|
771
965
|
|
772
966
|
**Requirement:**
|
773
967
|
|
774
|
-
To do this your application must have the [activemodel >= 3.2](https://rubygems.org/gems/activemodel) as a dependency.
|
968
|
+
To do this your application must have the [activemodel >= 3.2, < 6.1.0](https://rubygems.org/gems/activemodel) as a dependency.
|
775
969
|
|
776
970
|
```ruby
|
777
971
|
#
|
@@ -795,10 +989,10 @@ end
|
|
795
989
|
# your use cases on validation errors, you can use:
|
796
990
|
|
797
991
|
# In some file. e.g: A Rails initializer
|
798
|
-
require 'u-case/
|
992
|
+
require 'u-case/with_activemodel_validation' # or require 'micro/case/with_validation'
|
799
993
|
|
800
994
|
# In the Gemfile
|
801
|
-
gem 'u-case', require: 'u-case/
|
995
|
+
gem 'u-case', require: 'u-case/with_activemodel_validation'
|
802
996
|
|
803
997
|
# Using this approach, you can rewrite the previous example with less code. e.g:
|
804
998
|
|
@@ -823,7 +1017,7 @@ end
|
|
823
1017
|
Answer: Yes, it is. To do this, you only need to use the `disable_auto_validation` macro. e.g:
|
824
1018
|
|
825
1019
|
```ruby
|
826
|
-
require 'u-case/
|
1020
|
+
require 'u-case/with_activemodel_validation'
|
827
1021
|
|
828
1022
|
class Multiply < Micro::Case
|
829
1023
|
disable_auto_validation
|
@@ -845,6 +1039,31 @@ Multiply.call(a: 2, b: 'a')
|
|
845
1039
|
|
846
1040
|
[⬆️ Back to Top](#table-of-contents-)
|
847
1041
|
|
1042
|
+
#### Kind::Validator
|
1043
|
+
|
1044
|
+
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).
|
1045
|
+
|
1046
|
+
The example below shows how to validate the attributes data types.
|
1047
|
+
|
1048
|
+
```ruby
|
1049
|
+
class Todo::List::AddItem < Micro::Case
|
1050
|
+
attributes :user, :params
|
1051
|
+
|
1052
|
+
validates :user, kind: User
|
1053
|
+
validates :params, kind: ActionController::Parameters
|
1054
|
+
|
1055
|
+
def call!
|
1056
|
+
todo_params = Todo::Params.to_save(params)
|
1057
|
+
|
1058
|
+
todo = user.todos.create(todo_params)
|
1059
|
+
|
1060
|
+
Success { { todo: todo} }
|
1061
|
+
rescue ActionController::ParameterMissing => e
|
1062
|
+
Failure(:parameter_missing) { { message: e.message } }
|
1063
|
+
end
|
1064
|
+
end
|
1065
|
+
```
|
1066
|
+
|
848
1067
|
## Benchmarks
|
849
1068
|
|
850
1069
|
### `Micro::Case`
|
@@ -853,25 +1072,25 @@ Multiply.call(a: 2, b: 'a')
|
|
853
1072
|
|
854
1073
|
The table below contains the average between the [Success results](#success-results) and [Failure results](#failure-results) benchmarks.
|
855
1074
|
|
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
|
1075
|
+
| Gem / Abstraction | Iterations per second | Comparison |
|
1076
|
+
| ---------------------- | --------------------: | ----------------: |
|
1077
|
+
| **Micro::Case** | 116629.7 | _**The Fastest**_ |
|
1078
|
+
| Dry::Monads | 101796.3 | 1.14x slower |
|
1079
|
+
| Interactor | 21230.5 | 5.49x slower |
|
1080
|
+
| Trailblazer::Operation | 16466.6 | 7.08x slower |
|
1081
|
+
| Dry::Transaction | 5069.5 | 23.00x slower |
|
863
1082
|
|
864
1083
|
---
|
865
1084
|
|
866
1085
|
#### Success results
|
867
1086
|
|
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
|
1087
|
+
| Gem / Abstraction | Iterations per second | Comparison |
|
1088
|
+
| ----------------- | --------------------: | ----------------: |
|
1089
|
+
| Dry::Monads | 139352.5 | _**The Fastest**_ |
|
1090
|
+
| **Micro::Case** | 124749.4 | 1.12x slower |
|
1091
|
+
| Interactor | 28974.4 | 4.81x slower |
|
1092
|
+
| Trailblazer::Operation | 17275.6 | 8.07x slower |
|
1093
|
+
| Dry::Transaction | 5571.7 | 25.01x slower |
|
875
1094
|
|
876
1095
|
<details>
|
877
1096
|
<summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
|
@@ -911,13 +1130,13 @@ https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_success
|
|
911
1130
|
|
912
1131
|
#### Failure results
|
913
1132
|
|
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
|
1133
|
+
| Gem / Abstraction | Iterations per second | Comparison |
|
1134
|
+
| ----------------- | --------------------: | ----------------: |
|
1135
|
+
| **Micro::Case** | 108510.0 | _**The Fastest**_ |
|
1136
|
+
| Dry::Monads | 64240.1 | 1.69x slower |
|
1137
|
+
| Trailblazer::Operation | 15657.7 | 6.93x slower |
|
1138
|
+
| Interactor | 13486.7 | 8.05x slower |
|
1139
|
+
| Dry::Transaction | 4567.3 | 23.76x slower |
|
921
1140
|
|
922
1141
|
<details>
|
923
1142
|
<summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
|
@@ -960,10 +1179,10 @@ https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_failure
|
|
960
1179
|
### `Micro::Case::Flow`
|
961
1180
|
|
962
1181
|
| 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
|
1182
|
+
| ------------------ | ----------------: | ----------------: |
|
1183
|
+
| Micro::Case::Flow | _**The Fastest**_ | _**The Fastest**_ |
|
1184
|
+
| Micro::Case::Safe::Flow | 0x slower | 0x slower |
|
1185
|
+
| Interactor::Organizer | 1.47x slower | 5.51x slower |
|
967
1186
|
|
968
1187
|
\* The `Dry::Monads`, `Dry::Transaction`, `Trailblazer::Operation` are out of this analysis because all of them doesn't have this kind of feature.
|
969
1188
|
|
@@ -1022,13 +1241,27 @@ Check it out implementations of the same use case with different gems/abstractio
|
|
1022
1241
|
|
1023
1242
|
## Examples
|
1024
1243
|
|
1025
|
-
1
|
1026
|
-
|
1244
|
+
### 1️⃣ Rails App (API)
|
1245
|
+
|
1246
|
+
> 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.
|
1247
|
+
>
|
1248
|
+
> Link: https://github.com/serradura/from-fat-controllers-to-use-cases
|
1249
|
+
|
1250
|
+
### 2️⃣ CLI calculator
|
1251
|
+
|
1252
|
+
> Rake tasks to demonstrate how to handle user data, and how to use different failure types to control the program flow.
|
1253
|
+
>
|
1254
|
+
> Link: https://github.com/serradura/u-case/tree/master/examples/calculator
|
1255
|
+
|
1256
|
+
### 3️⃣ Users creation
|
1257
|
+
|
1258
|
+
> An example of a use case flow that define steps to sanitize, validate, and persist its input data.
|
1259
|
+
>
|
1260
|
+
> Link: https://github.com/serradura/u-case/blob/master/examples/users_creation.rb
|
1027
1261
|
|
1028
|
-
|
1029
|
-
3. [CLI calculator](https://github.com/serradura/u-case/tree/master/examples/calculator)
|
1262
|
+
### 4️⃣ Rescuing exception inside of the use cases
|
1030
1263
|
|
1031
|
-
|
1264
|
+
> Link: https://github.com/serradura/u-case/blob/master/examples/rescuing_exceptions.rb
|
1032
1265
|
|
1033
1266
|
[⬆️ Back to Top](#table-of-contents-)
|
1034
1267
|
|
data/lib/micro/case.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'kind'
|
3
4
|
require 'micro/attributes'
|
4
|
-
# frozen_string_literal: true
|
5
5
|
|
6
6
|
module Micro
|
7
7
|
class Case
|
8
8
|
require 'micro/case/version'
|
9
|
+
require 'micro/case/utils'
|
9
10
|
require 'micro/case/result'
|
10
11
|
require 'micro/case/error'
|
11
12
|
require 'micro/case/safe'
|
@@ -42,23 +43,15 @@ module Micro
|
|
42
43
|
instance
|
43
44
|
end
|
44
45
|
|
45
|
-
def self.
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
private_class_method def self.__set_flow__(reducer, args)
|
50
|
-
def self.use_cases; __get_flow__.use_cases; end
|
51
|
-
|
52
|
-
self.class_eval('def use_cases; self.class.use_cases; end')
|
53
|
-
|
54
|
-
reducer.build(args)
|
55
|
-
end
|
46
|
+
def self.__call_and_set_transition__(result, arg)
|
47
|
+
if arg.respond_to?(:keys)
|
48
|
+
result.__set_transitions_accessible_attributes__(arg.keys)
|
49
|
+
end
|
56
50
|
|
57
|
-
|
58
|
-
@__flow ||= __set_flow__(Flow::Reducer, args)
|
51
|
+
__new__(result, arg).call
|
59
52
|
end
|
60
53
|
|
61
|
-
def self.
|
54
|
+
def self.__call!
|
62
55
|
return const_get(:Flow_Step) if const_defined?(:Flow_Step)
|
63
56
|
|
64
57
|
const_set(:Flow_Step, Class.new(self) do
|
@@ -68,9 +61,51 @@ module Micro
|
|
68
61
|
end)
|
69
62
|
end
|
70
63
|
|
64
|
+
def self.call!
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.__flow_reducer
|
69
|
+
Flow::Reducer
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.__flow_get
|
73
|
+
return @__flow if defined?(@__flow)
|
74
|
+
end
|
75
|
+
|
76
|
+
private_class_method def self.__flow_use_cases
|
77
|
+
return @__flow_use_cases if defined?(@__flow_use_cases)
|
78
|
+
end
|
79
|
+
|
80
|
+
private_class_method def self.__flow_use_cases_get
|
81
|
+
Array(__flow_use_cases)
|
82
|
+
.map { |use_case| use_case == self ? self.__call! : use_case }
|
83
|
+
end
|
84
|
+
|
85
|
+
private_class_method def self.__flow_use_cases_set(args)
|
86
|
+
@__flow_use_cases = args
|
87
|
+
end
|
88
|
+
|
89
|
+
private_class_method def self.__flow_set(args)
|
90
|
+
return if __flow_get
|
91
|
+
|
92
|
+
def self.use_cases; __flow_get.use_cases; end
|
93
|
+
|
94
|
+
self.class_eval('def use_cases; self.class.use_cases; end')
|
95
|
+
|
96
|
+
@__flow = __flow_reducer.build(args)
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.__flow_set!
|
100
|
+
__flow_set(__flow_use_cases_get) if !__flow_get && __flow_use_cases
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.flow(*args)
|
104
|
+
__flow_use_cases_set(args)
|
105
|
+
end
|
106
|
+
|
71
107
|
def initialize(input)
|
72
|
-
|
73
|
-
self.attributes = input
|
108
|
+
__setup_use_case(input)
|
74
109
|
end
|
75
110
|
|
76
111
|
def call!
|
@@ -83,15 +118,23 @@ module Micro
|
|
83
118
|
|
84
119
|
def __set_result__(result)
|
85
120
|
raise Error::InvalidResultInstance unless result.is_a?(Result)
|
86
|
-
raise Error::ResultIsAlreadyDefined if @__result
|
121
|
+
raise Error::ResultIsAlreadyDefined if defined?(@__result)
|
87
122
|
|
88
123
|
@__result = result
|
89
124
|
end
|
90
125
|
|
91
126
|
private
|
92
127
|
|
128
|
+
def __setup_use_case(input)
|
129
|
+
self.class.__flow_set!
|
130
|
+
|
131
|
+
@__input = input
|
132
|
+
|
133
|
+
self.attributes = input
|
134
|
+
end
|
135
|
+
|
93
136
|
def __call
|
94
|
-
return
|
137
|
+
return __call_use_case_flow if __call_use_case_flow?
|
95
138
|
|
96
139
|
__call_use_case
|
97
140
|
end
|
@@ -104,21 +147,25 @@ module Micro
|
|
104
147
|
raise Error::UnexpectedResult.new(self.class)
|
105
148
|
end
|
106
149
|
|
150
|
+
def __call_use_case_flow?
|
151
|
+
self.class.__flow_get
|
152
|
+
end
|
153
|
+
|
154
|
+
def __call_use_case_flow
|
155
|
+
self.class.__flow_get.call(@__input)
|
156
|
+
end
|
157
|
+
|
107
158
|
def Success(arg = :ok)
|
108
159
|
value, type = block_given? ? [yield, arg] : [arg, :ok]
|
109
160
|
|
110
|
-
|
161
|
+
__get_result_with(true, value, type)
|
111
162
|
end
|
112
163
|
|
113
164
|
def Failure(arg = :error)
|
114
165
|
value = block_given? ? yield : arg
|
115
166
|
type = __map_failure_type(value, block_given? ? arg : :error)
|
116
167
|
|
117
|
-
|
118
|
-
end
|
119
|
-
|
120
|
-
def __get_result__
|
121
|
-
@__result ||= Result.new
|
168
|
+
__get_result_with(false, value, type)
|
122
169
|
end
|
123
170
|
|
124
171
|
def __map_failure_type(arg, type)
|
@@ -128,5 +175,13 @@ module Micro
|
|
128
175
|
|
129
176
|
type
|
130
177
|
end
|
178
|
+
|
179
|
+
def __get_result__
|
180
|
+
@__result ||= Result.new
|
181
|
+
end
|
182
|
+
|
183
|
+
def __get_result_with(is_success, value, type)
|
184
|
+
__get_result__.__set__(is_success, value, type, self)
|
185
|
+
end
|
131
186
|
end
|
132
187
|
end
|
data/lib/micro/case/error.rb
CHANGED
@@ -9,20 +9,36 @@ module Micro
|
|
9
9
|
def initialize(klass); super(klass.name + MESSAGE); end
|
10
10
|
end
|
11
11
|
|
12
|
-
ResultIsAlreadyDefined
|
12
|
+
class ResultIsAlreadyDefined < ArgumentError
|
13
|
+
def initialize; super('result is already defined'.freeze); end
|
14
|
+
end
|
13
15
|
|
14
|
-
InvalidResultType
|
15
|
-
|
16
|
+
class InvalidResultType < TypeError
|
17
|
+
def initialize; super('type must be a Symbol'.freeze); end
|
18
|
+
end
|
16
19
|
|
17
|
-
|
18
|
-
|
20
|
+
class InvalidResultInstance < ArgumentError
|
21
|
+
def initialize; super('argument must be an instance of Micro::Case::Result'.freeze); end
|
22
|
+
end
|
19
23
|
|
20
|
-
|
24
|
+
class InvalidUseCase < TypeError
|
25
|
+
def initialize; super('use case must be a kind or an instance of Micro::Case'.freeze); end
|
26
|
+
end
|
21
27
|
|
22
|
-
class
|
23
|
-
|
28
|
+
class InvalidUseCases < ArgumentError
|
29
|
+
def initialize; super('argument must be a collection of `Micro::Case` classes'.freeze); end
|
30
|
+
end
|
24
31
|
|
25
|
-
|
32
|
+
class InvalidInvocationOfTheThenMethod < StandardError
|
33
|
+
def initialize; super('Invalid invocation of the Micro::Case::Result#then method'); end
|
34
|
+
end
|
35
|
+
|
36
|
+
class UndefinedFlow < ArgumentError
|
37
|
+
def initialize; super("This class hasn't declared its flow. Please, use the `flow()` macro to define one.".freeze); end
|
38
|
+
end
|
39
|
+
|
40
|
+
class InvalidAccessToTheUseCaseObject < StandardError
|
41
|
+
def initialize; super('only a failure result can access its use case object'.freeze); end
|
26
42
|
end
|
27
43
|
|
28
44
|
module ByWrongUsage
|
data/lib/micro/case/flow.rb
CHANGED
@@ -23,19 +23,18 @@ module Micro
|
|
23
23
|
|
24
24
|
def initialize(use_cases)
|
25
25
|
@use_cases = use_cases
|
26
|
+
@first_use_case = use_cases[0]
|
27
|
+
@next_use_cases = use_cases[1..-1]
|
26
28
|
end
|
27
29
|
|
28
30
|
def call(arg = {})
|
29
31
|
memo = arg.is_a?(Hash) ? arg.dup : {}
|
30
32
|
|
31
|
-
|
32
|
-
break result if result.failure?
|
33
|
+
first_result = first_use_case_result(arg)
|
33
34
|
|
34
|
-
|
35
|
-
input = value.is_a?(Hash) ? memo.tap { |data| data.merge!(value) } : value
|
35
|
+
return first_result if @next_use_cases.empty?
|
36
36
|
|
37
|
-
|
38
|
-
end
|
37
|
+
next_use_cases_result(first_result, memo)
|
39
38
|
end
|
40
39
|
|
41
40
|
def >>(arg)
|
@@ -52,16 +51,8 @@ module Micro
|
|
52
51
|
|
53
52
|
private
|
54
53
|
|
55
|
-
def
|
56
|
-
|
57
|
-
end
|
58
|
-
|
59
|
-
def initial_result(arg)
|
60
|
-
return arg.call if arg_to_call?(arg)
|
61
|
-
return arg if arg.is_a?(Micro::Case::Result)
|
62
|
-
|
63
|
-
result = ::Micro::Case::Result.new
|
64
|
-
result.__set__(true, arg, :ok, nil)
|
54
|
+
def is_a_result?(arg)
|
55
|
+
arg.is_a?(Micro::Case::Result)
|
65
56
|
end
|
66
57
|
|
67
58
|
def arg_to_call?(arg)
|
@@ -69,6 +60,44 @@ module Micro
|
|
69
60
|
return true if arg.is_a?(Class) && (arg < ::Micro::Case || arg < ::Micro::Case::Flow)
|
70
61
|
return false
|
71
62
|
end
|
63
|
+
|
64
|
+
def call_arg(arg)
|
65
|
+
output = arg.call
|
66
|
+
|
67
|
+
is_a_result?(output) ? output.value : output
|
68
|
+
end
|
69
|
+
|
70
|
+
def first_use_case_input(arg)
|
71
|
+
return call_arg(arg) if arg_to_call?(arg)
|
72
|
+
return arg.value if is_a_result?(arg)
|
73
|
+
|
74
|
+
arg
|
75
|
+
end
|
76
|
+
|
77
|
+
def first_use_case_result(arg)
|
78
|
+
input = first_use_case_input(arg)
|
79
|
+
|
80
|
+
result = ::Micro::Case::Result.new
|
81
|
+
|
82
|
+
@first_use_case.__call_and_set_transition__(result, input)
|
83
|
+
end
|
84
|
+
|
85
|
+
def next_use_case_result(use_case, result, input)
|
86
|
+
use_case.__new__(result, input).call
|
87
|
+
end
|
88
|
+
|
89
|
+
def next_use_cases_result(first_result, memo)
|
90
|
+
@next_use_cases.reduce(first_result) do |result, use_case|
|
91
|
+
break result if result.failure?
|
92
|
+
|
93
|
+
value = result.value
|
94
|
+
input = value.is_a?(Hash) ? memo.tap { |data| data.merge!(value) } : value
|
95
|
+
|
96
|
+
result.__set_transitions_accessible_attributes__(memo.keys)
|
97
|
+
|
98
|
+
next_use_case_result(use_case, result, input)
|
99
|
+
end
|
100
|
+
end
|
72
101
|
end
|
73
102
|
end
|
74
103
|
end
|
data/lib/micro/case/result.rb
CHANGED
@@ -1,8 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'set'
|
4
|
+
|
3
5
|
module Micro
|
4
6
|
class Case
|
5
7
|
class Result
|
8
|
+
Kind::Types.add(self)
|
9
|
+
|
10
|
+
@@transition_tracking_disabled = false
|
11
|
+
|
12
|
+
def self.disable_transition_tracking
|
13
|
+
@@transition_tracking_disabled = true
|
14
|
+
end
|
15
|
+
|
6
16
|
class Data
|
7
17
|
attr_reader :value, :type
|
8
18
|
|
@@ -17,12 +27,19 @@ module Micro
|
|
17
27
|
|
18
28
|
attr_reader :value, :type
|
19
29
|
|
30
|
+
def initialize
|
31
|
+
@__transitions__ = {}
|
32
|
+
@__transitions_accessible_attributes__ = Set.new
|
33
|
+
end
|
34
|
+
|
20
35
|
def __set__(is_success, value, type, use_case)
|
21
36
|
raise Error::InvalidResultType unless type.is_a?(Symbol)
|
22
|
-
raise Error::InvalidUseCase if !
|
37
|
+
raise Error::InvalidUseCase if !is_a_use_case?(use_case)
|
23
38
|
|
24
39
|
@success, @value, @type, @use_case = is_success, value, type, use_case
|
25
40
|
|
41
|
+
__set_transition__ unless @@transition_tracking_disabled
|
42
|
+
|
26
43
|
self
|
27
44
|
end
|
28
45
|
|
@@ -52,8 +69,47 @@ module Micro
|
|
52
69
|
self.tap { yield(data, @use_case) }
|
53
70
|
end
|
54
71
|
|
72
|
+
def then(arg = nil, &block)
|
73
|
+
can_yield_self = respond_to?(:yield_self)
|
74
|
+
|
75
|
+
if block
|
76
|
+
raise Error::InvalidInvocationOfTheThenMethod if arg
|
77
|
+
raise NotImplementedError if !can_yield_self
|
78
|
+
|
79
|
+
yield_self(&block)
|
80
|
+
else
|
81
|
+
return yield_self if !arg && can_yield_self
|
82
|
+
|
83
|
+
raise Error::InvalidInvocationOfTheThenMethod if !is_a_use_case?(arg)
|
84
|
+
|
85
|
+
return self if failure?
|
86
|
+
|
87
|
+
arg.__call_and_set_transition__(self, self.value)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def transitions
|
92
|
+
return [] if @__transitions__.empty?
|
93
|
+
|
94
|
+
@__transitions__.map { |_use_case, transition| transition }
|
95
|
+
end
|
96
|
+
|
97
|
+
def __set_transitions_accessible_attributes__(attribute_names)
|
98
|
+
return if @@transition_tracking_disabled
|
99
|
+
|
100
|
+
__set_transitions_accessible_attributes__!(
|
101
|
+
attribute_names.map!(&:to_sym)
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
55
105
|
private
|
56
106
|
|
107
|
+
def __set_transitions_accessible_attributes__!(attribute_names)
|
108
|
+
@__transitions_accessible_attributes__.merge(
|
109
|
+
attribute_names
|
110
|
+
)
|
111
|
+
end
|
112
|
+
|
57
113
|
def success_type?(expected_type)
|
58
114
|
success? && (expected_type.nil? || expected_type == type)
|
59
115
|
end
|
@@ -65,6 +121,22 @@ module Micro
|
|
65
121
|
def is_a_use_case?(arg)
|
66
122
|
(arg.is_a?(Class) && arg < ::Micro::Case) || arg.is_a?(::Micro::Case)
|
67
123
|
end
|
124
|
+
|
125
|
+
def __set_transition__
|
126
|
+
use_case_class = @use_case.class
|
127
|
+
use_case_attributes = Utils.symbolize_keys(@use_case.attributes)
|
128
|
+
|
129
|
+
__set_transitions_accessible_attributes__!(use_case_attributes.keys)
|
130
|
+
|
131
|
+
result = @success ? :success : :failure
|
132
|
+
transition = {
|
133
|
+
use_case: { class: use_case_class, attributes: use_case_attributes },
|
134
|
+
result => { type: @type, value: @value },
|
135
|
+
accessible_attributes: @__transitions_accessible_attributes__.to_a
|
136
|
+
}
|
137
|
+
|
138
|
+
@__transitions__[use_case_class] = transition
|
139
|
+
end
|
68
140
|
end
|
69
141
|
end
|
70
142
|
end
|
data/lib/micro/case/safe.rb
CHANGED
data/lib/micro/case/safe/flow.rb
CHANGED
@@ -19,7 +19,7 @@ module Micro
|
|
19
19
|
|
20
20
|
private
|
21
21
|
|
22
|
-
def
|
22
|
+
def next_use_case_result(use_case, result, input)
|
23
23
|
begin
|
24
24
|
instance = use_case.__new__(result, input)
|
25
25
|
instance.call
|
@@ -36,8 +36,8 @@ module Micro
|
|
36
36
|
Flow::Reducer.build(Array(args))
|
37
37
|
end
|
38
38
|
|
39
|
-
def self.
|
40
|
-
|
39
|
+
def self.__flow_reducer
|
40
|
+
Flow::Reducer
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Micro
|
4
|
+
class Case
|
5
|
+
module Utils
|
6
|
+
def self.symbolize_keys(hash)
|
7
|
+
if Kind.of.Hash(hash).respond_to?(:transform_keys)
|
8
|
+
hash.transform_keys { |key| key.to_sym rescue key }
|
9
|
+
else
|
10
|
+
hash.each_with_object({}) do |(k, v), memo|
|
11
|
+
key = k.to_sym rescue k
|
12
|
+
|
13
|
+
memo[key] = v
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/micro/case/version.rb
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'micro/case'
|
4
|
+
|
5
|
+
module Micro
|
6
|
+
class Case
|
7
|
+
include Micro::Attributes::Features::ActiveModelValidations
|
8
|
+
|
9
|
+
def self.auto_validation_disabled?
|
10
|
+
return @disable_auto_validation if defined?(@disable_auto_validation)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.disable_auto_validation
|
14
|
+
@disable_auto_validation = true
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(input)
|
18
|
+
__setup_use_case(input)
|
19
|
+
|
20
|
+
run_validations! if respond_to?(:run_validations!, true)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def __call_use_case
|
26
|
+
return failure_by_validation_error(self) if !self.class.auto_validation_disabled? && invalid?
|
27
|
+
|
28
|
+
result = call!
|
29
|
+
|
30
|
+
return result if result.is_a?(Result)
|
31
|
+
|
32
|
+
raise Error::UnexpectedResult.new(self.class)
|
33
|
+
end
|
34
|
+
|
35
|
+
def failure_by_validation_error(object)
|
36
|
+
errors = object.respond_to?(:errors) ? object.errors : object
|
37
|
+
|
38
|
+
Failure(:validation_error) { { errors: errors } }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -1,3 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
warn 'Deprecation: "u-case/with_validation" will be deprecated in the next major release.' \
|
4
|
+
'Please use "u-case/with_activemodel_validation" instead of it.'
|
5
|
+
|
6
|
+
require 'u-case/with_activemodel_validation'
|
data/u-case.gemspec
CHANGED
@@ -14,20 +14,8 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.homepage = 'https://github.com/serradura/u-case'
|
15
15
|
spec.license = 'MIT'
|
16
16
|
|
17
|
-
|
18
|
-
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
-
if spec.respond_to?(:metadata)
|
20
|
-
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
21
|
-
|
22
|
-
# spec.metadata["homepage_uri"] = spec.homepage
|
23
|
-
# spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
|
24
|
-
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
25
|
-
else
|
26
|
-
raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
|
27
|
-
end
|
17
|
+
raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.' unless spec.respond_to?(:metadata)
|
28
18
|
|
29
|
-
# Specify which files should be added to the gem when it is released.
|
30
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
31
19
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
32
20
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|assets|benchmarks|comparisons|examples)/}) }
|
33
21
|
end
|
@@ -37,8 +25,9 @@ Gem::Specification.new do |spec|
|
|
37
25
|
|
38
26
|
spec.required_ruby_version = '>= 2.2.0'
|
39
27
|
|
28
|
+
spec.add_runtime_dependency 'kind', '~> 3.0'
|
40
29
|
spec.add_runtime_dependency 'u-attributes', '~> 1.1'
|
41
30
|
|
42
31
|
spec.add_development_dependency 'bundler'
|
43
|
-
spec.add_development_dependency 'rake', '~>
|
32
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
44
33
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: u-case
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rodrigo Serradura
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-06-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: kind
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: u-attributes
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,14 +58,14 @@ dependencies:
|
|
44
58
|
requirements:
|
45
59
|
- - "~>"
|
46
60
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
61
|
+
version: '13.0'
|
48
62
|
type: :development
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
52
66
|
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
68
|
+
version: '13.0'
|
55
69
|
description: Create simple and powerful use cases as objects.
|
56
70
|
email:
|
57
71
|
- rodrigo.serradura@gmail.com
|
@@ -78,9 +92,11 @@ files:
|
|
78
92
|
- lib/micro/case/safe.rb
|
79
93
|
- lib/micro/case/safe/flow.rb
|
80
94
|
- lib/micro/case/strict.rb
|
95
|
+
- lib/micro/case/utils.rb
|
81
96
|
- lib/micro/case/version.rb
|
82
|
-
- lib/micro/case/
|
97
|
+
- lib/micro/case/with_activemodel_validation.rb
|
83
98
|
- lib/u-case.rb
|
99
|
+
- lib/u-case/with_activemodel_validation.rb
|
84
100
|
- lib/u-case/with_validation.rb
|
85
101
|
- test.sh
|
86
102
|
- u-case.gemspec
|
@@ -103,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
103
119
|
- !ruby/object:Gem::Version
|
104
120
|
version: '0'
|
105
121
|
requirements: []
|
106
|
-
rubygems_version: 3.0.
|
122
|
+
rubygems_version: 3.0.6
|
107
123
|
signing_key:
|
108
124
|
specification_version: 4
|
109
125
|
summary: Create simple and powerful use cases as objects.
|
@@ -1,35 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'micro/case'
|
4
|
-
|
5
|
-
module Micro
|
6
|
-
class Case
|
7
|
-
include Micro::Attributes::Features::ActiveModelValidations
|
8
|
-
|
9
|
-
def self.auto_validation_disabled?
|
10
|
-
@disable_auto_validation
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.disable_auto_validation
|
14
|
-
@disable_auto_validation = true
|
15
|
-
end
|
16
|
-
|
17
|
-
def initialize(input)
|
18
|
-
@__input = input
|
19
|
-
self.attributes = input
|
20
|
-
run_validations! if respond_to?(:run_validations!, true)
|
21
|
-
end
|
22
|
-
|
23
|
-
def call
|
24
|
-
return failure_by_validation_error(self) if !self.class.auto_validation_disabled? && invalid?
|
25
|
-
|
26
|
-
__call
|
27
|
-
end
|
28
|
-
|
29
|
-
private def failure_by_validation_error(object)
|
30
|
-
errors = object.respond_to?(:errors) ? object.errors : object
|
31
|
-
|
32
|
-
Failure(:validation_error) { { errors: errors } }
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|