u-case 2.3.1 → 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/Gemfile +13 -1
- data/README.md +245 -75
- data/lib/micro/case.rb +30 -13
- data/lib/micro/case/flow.rb +1 -1
- data/lib/micro/case/flow/reducer.rb +45 -16
- data/lib/micro/case/result.rb +55 -2
- data/lib/micro/case/safe/flow.rb +1 -1
- 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} +1 -1
- data/lib/u-case/with_activemodel_validation.rb +5 -0
- data/lib/u-case/with_validation.rb +4 -1
- data/u-case.gemspec +2 -1
- metadata +21 -5
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/Gemfile
CHANGED
@@ -18,7 +18,19 @@ 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
|
+
group :development, :test do
|
31
|
+
gem 'awesome_print', '~> 1.8'
|
32
|
+
|
33
|
+
gem 'pry-byebug', "~> #{pry_byebug_version}"
|
22
34
|
end
|
23
35
|
|
24
36
|
# Specify your gem's dependencies in u-case.gemspec
|
data/README.md
CHANGED
@@ -1,15 +1,16 @@
|
|
1
|
+

|
1
2
|
[](https://rubygems.org/gems/u-case)
|
2
3
|
[](https://travis-ci.com/serradura/u-case)
|
3
4
|
[](https://codeclimate.com/github/serradura/u-case/maintainability)
|
4
5
|
[](https://codeclimate.com/github/serradura/u-case/test_coverage)
|
5
6
|
|
6
|
-
μ-case (Micro::Case)
|
7
|
-
|
7
|
+
μ-case (Micro::Case) <!-- omit in toc -->
|
8
|
+
====================
|
8
9
|
|
9
10
|
Create simple and powerful use cases as objects.
|
10
11
|
|
11
12
|
The main project goals are:
|
12
|
-
1.
|
13
|
+
1. Easy to use and easy to learn (input **>>** process **>>** output).
|
13
14
|
2. Promote referential transparency (transforming instead of modifying) and data integrity.
|
14
15
|
3. No callbacks (e.g: before, after, around).
|
15
16
|
4. Solve complex business logic, by allowing the composition of use cases.
|
@@ -18,44 +19,46 @@ The main project goals are:
|
|
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
|
|
20
21
|
## 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
|
-
- [
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
- [
|
49
|
-
- [
|
50
|
-
- [
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
- [
|
56
|
-
- [
|
57
|
-
|
58
|
-
|
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)
|
59
62
|
|
60
63
|
## Required Ruby version
|
61
64
|
|
@@ -63,8 +66,15 @@ The main project goals are:
|
|
63
66
|
|
64
67
|
## Dependencies
|
65
68
|
|
66
|
-
|
67
|
-
|
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.
|
68
78
|
|
69
79
|
## Installation
|
70
80
|
|
@@ -642,12 +652,147 @@ Note: You can blend any of the [available syntaxes/approaches](#how-to-create-a-
|
|
642
652
|
|
643
653
|
[⬆️ Back to Top](#table-of-contents-)
|
644
654
|
|
645
|
-
#### 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:
|
646
702
|
|
647
|
-
|
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.
|
648
722
|
|
649
723
|
[⬆️ Back to Top](#table-of-contents-)
|
650
724
|
|
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
|
+
|
651
796
|
#### Is it possible to declare a flow which includes the use case itself?
|
652
797
|
|
653
798
|
Answer: Yes, it is! You can use the `self.call!` macro. e.g:
|
@@ -816,11 +961,11 @@ end
|
|
816
961
|
|
817
962
|
[⬆️ Back to Top](#table-of-contents-)
|
818
963
|
|
819
|
-
### `u-case/
|
964
|
+
### `u-case/with_activemodel_validation` - How to validate use case attributes?
|
820
965
|
|
821
966
|
**Requirement:**
|
822
967
|
|
823
|
-
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.
|
824
969
|
|
825
970
|
```ruby
|
826
971
|
#
|
@@ -844,10 +989,10 @@ end
|
|
844
989
|
# your use cases on validation errors, you can use:
|
845
990
|
|
846
991
|
# In some file. e.g: A Rails initializer
|
847
|
-
require 'u-case/
|
992
|
+
require 'u-case/with_activemodel_validation' # or require 'micro/case/with_validation'
|
848
993
|
|
849
994
|
# In the Gemfile
|
850
|
-
gem 'u-case', require: 'u-case/
|
995
|
+
gem 'u-case', require: 'u-case/with_activemodel_validation'
|
851
996
|
|
852
997
|
# Using this approach, you can rewrite the previous example with less code. e.g:
|
853
998
|
|
@@ -872,7 +1017,7 @@ end
|
|
872
1017
|
Answer: Yes, it is. To do this, you only need to use the `disable_auto_validation` macro. e.g:
|
873
1018
|
|
874
1019
|
```ruby
|
875
|
-
require 'u-case/
|
1020
|
+
require 'u-case/with_activemodel_validation'
|
876
1021
|
|
877
1022
|
class Multiply < Micro::Case
|
878
1023
|
disable_auto_validation
|
@@ -894,6 +1039,31 @@ Multiply.call(a: 2, b: 'a')
|
|
894
1039
|
|
895
1040
|
[⬆️ Back to Top](#table-of-contents-)
|
896
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
|
+
|
897
1067
|
## Benchmarks
|
898
1068
|
|
899
1069
|
### `Micro::Case`
|
@@ -902,25 +1072,25 @@ Multiply.call(a: 2, b: 'a')
|
|
902
1072
|
|
903
1073
|
The table below contains the average between the [Success results](#success-results) and [Failure results](#failure-results) benchmarks.
|
904
1074
|
|
905
|
-
| Gem / Abstraction | Iterations per second | Comparison
|
906
|
-
| ---------------------- | --------------------: |
|
907
|
-
| **Micro::Case** | 116629.7 | _**The
|
908
|
-
| Dry::Monads | 101796.3 | 1.14x slower
|
909
|
-
| Interactor | 21230.5 | 5.49x slower
|
910
|
-
| Trailblazer::Operation | 16466.6 | 7.08x slower
|
911
|
-
| 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 |
|
912
1082
|
|
913
1083
|
---
|
914
1084
|
|
915
1085
|
#### Success results
|
916
1086
|
|
917
|
-
| Gem / Abstraction | Iterations per second | Comparison
|
918
|
-
| ----------------- | --------------------: |
|
919
|
-
| Dry::Monads | 139352.5 | _**The
|
920
|
-
| **Micro::Case** | 124749.4 | 1.12x slower
|
921
|
-
| Interactor | 28974.4 | 4.81x slower
|
922
|
-
| Trailblazer::Operation | 17275.6 | 8.07x slower
|
923
|
-
| 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 |
|
924
1094
|
|
925
1095
|
<details>
|
926
1096
|
<summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
|
@@ -960,13 +1130,13 @@ https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_success
|
|
960
1130
|
|
961
1131
|
#### Failure results
|
962
1132
|
|
963
|
-
| Gem / Abstraction | Iterations per second | Comparison
|
964
|
-
| ----------------- | --------------------: |
|
965
|
-
| **Micro::Case** | 108510.0 | _**The
|
966
|
-
| Dry::Monads | 64240.1 | 1.69x slower
|
967
|
-
| Trailblazer::Operation | 15657.7 | 6.93x slower
|
968
|
-
| Interactor | 13486.7 | 8.05x slower
|
969
|
-
| 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 |
|
970
1140
|
|
971
1141
|
<details>
|
972
1142
|
<summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
|
@@ -1009,10 +1179,10 @@ https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_failure
|
|
1009
1179
|
### `Micro::Case::Flow`
|
1010
1180
|
|
1011
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) |
|
1012
|
-
| ------------------ |
|
1013
|
-
| Micro::Case::Flow | _**The
|
1014
|
-
| Micro::Case::Safe::Flow | 0x slower
|
1015
|
-
| 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 |
|
1016
1186
|
|
1017
1187
|
\* The `Dry::Monads`, `Dry::Transaction`, `Trailblazer::Operation` are out of this analysis because all of them doesn't have this kind of feature.
|
1018
1188
|
|
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,6 +43,14 @@ module Micro
|
|
42
43
|
instance
|
43
44
|
end
|
44
45
|
|
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
|
50
|
+
|
51
|
+
__new__(result, arg).call
|
52
|
+
end
|
53
|
+
|
45
54
|
def self.__call!
|
46
55
|
return const_get(:Flow_Step) if const_defined?(:Flow_Step)
|
47
56
|
|
@@ -61,18 +70,22 @@ module Micro
|
|
61
70
|
end
|
62
71
|
|
63
72
|
def self.__flow_get
|
64
|
-
@__flow
|
73
|
+
return @__flow if defined?(@__flow)
|
65
74
|
end
|
66
75
|
|
67
|
-
private_class_method def self.
|
68
|
-
@__flow_use_cases
|
76
|
+
private_class_method def self.__flow_use_cases
|
77
|
+
return @__flow_use_cases if defined?(@__flow_use_cases)
|
69
78
|
end
|
70
79
|
|
71
80
|
private_class_method def self.__flow_use_cases_get
|
72
|
-
Array(
|
81
|
+
Array(__flow_use_cases)
|
73
82
|
.map { |use_case| use_case == self ? self.__call! : use_case }
|
74
83
|
end
|
75
84
|
|
85
|
+
private_class_method def self.__flow_use_cases_set(args)
|
86
|
+
@__flow_use_cases = args
|
87
|
+
end
|
88
|
+
|
76
89
|
private_class_method def self.__flow_set(args)
|
77
90
|
return if __flow_get
|
78
91
|
|
@@ -84,7 +97,7 @@ module Micro
|
|
84
97
|
end
|
85
98
|
|
86
99
|
def self.__flow_set!
|
87
|
-
__flow_set(__flow_use_cases_get) if !__flow_get &&
|
100
|
+
__flow_set(__flow_use_cases_get) if !__flow_get && __flow_use_cases
|
88
101
|
end
|
89
102
|
|
90
103
|
def self.flow(*args)
|
@@ -105,7 +118,7 @@ module Micro
|
|
105
118
|
|
106
119
|
def __set_result__(result)
|
107
120
|
raise Error::InvalidResultInstance unless result.is_a?(Result)
|
108
|
-
raise Error::ResultIsAlreadyDefined if @__result
|
121
|
+
raise Error::ResultIsAlreadyDefined if defined?(@__result)
|
109
122
|
|
110
123
|
@__result = result
|
111
124
|
end
|
@@ -145,18 +158,14 @@ module Micro
|
|
145
158
|
def Success(arg = :ok)
|
146
159
|
value, type = block_given? ? [yield, arg] : [arg, :ok]
|
147
160
|
|
148
|
-
|
161
|
+
__get_result_with(true, value, type)
|
149
162
|
end
|
150
163
|
|
151
164
|
def Failure(arg = :error)
|
152
165
|
value = block_given? ? yield : arg
|
153
166
|
type = __map_failure_type(value, block_given? ? arg : :error)
|
154
167
|
|
155
|
-
|
156
|
-
end
|
157
|
-
|
158
|
-
def __get_result__
|
159
|
-
@__result ||= Result.new
|
168
|
+
__get_result_with(false, value, type)
|
160
169
|
end
|
161
170
|
|
162
171
|
def __map_failure_type(arg, type)
|
@@ -166,5 +175,13 @@ module Micro
|
|
166
175
|
|
167
176
|
type
|
168
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
|
169
186
|
end
|
170
187
|
end
|
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
|
|
@@ -67,12 +84,32 @@ module Micro
|
|
67
84
|
|
68
85
|
return self if failure?
|
69
86
|
|
70
|
-
arg.
|
87
|
+
arg.__call_and_set_transition__(self, self.value)
|
71
88
|
end
|
72
89
|
end
|
73
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
|
+
|
74
105
|
private
|
75
106
|
|
107
|
+
def __set_transitions_accessible_attributes__!(attribute_names)
|
108
|
+
@__transitions_accessible_attributes__.merge(
|
109
|
+
attribute_names
|
110
|
+
)
|
111
|
+
end
|
112
|
+
|
76
113
|
def success_type?(expected_type)
|
77
114
|
success? && (expected_type.nil? || expected_type == type)
|
78
115
|
end
|
@@ -84,6 +121,22 @@ module Micro
|
|
84
121
|
def is_a_use_case?(arg)
|
85
122
|
(arg.is_a?(Class) && arg < ::Micro::Case) || arg.is_a?(::Micro::Case)
|
86
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
|
87
140
|
end
|
88
141
|
end
|
89
142
|
end
|
data/lib/micro/case/safe/flow.rb
CHANGED
@@ -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
@@ -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
@@ -25,8 +25,9 @@ Gem::Specification.new do |spec|
|
|
25
25
|
|
26
26
|
spec.required_ruby_version = '>= 2.2.0'
|
27
27
|
|
28
|
+
spec.add_runtime_dependency 'kind', '~> 3.0'
|
28
29
|
spec.add_runtime_dependency 'u-attributes', '~> 1.1'
|
29
30
|
|
30
31
|
spec.add_development_dependency 'bundler'
|
31
|
-
spec.add_development_dependency 'rake', '~>
|
32
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
32
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
|