u-case 2.3.0 → 3.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.sh +8 -2
- data/.travis.yml +1 -0
- data/Gemfile +22 -1
- data/README.md +586 -331
- data/lib/micro/case.rb +72 -57
- data/lib/micro/case/error.rb +17 -12
- data/lib/micro/case/result.rb +103 -18
- data/lib/micro/case/safe.rb +6 -2
- data/lib/micro/case/utils.rb +19 -0
- data/lib/micro/case/version.rb +1 -1
- data/lib/micro/case/{with_validation.rb → with_activemodel_validation.rb} +8 -6
- data/lib/micro/cases.rb +16 -0
- data/lib/micro/cases/flow.rb +96 -0
- data/lib/micro/cases/safe/flow.rb +18 -0
- data/lib/u-case/with_activemodel_validation.rb +5 -0
- data/lib/u-case/with_validation.rb +4 -1
- data/u-case.gemspec +3 -14
- metadata +26 -10
- data/lib/micro/case/flow.rb +0 -48
- data/lib/micro/case/flow/reducer.rb +0 -75
- data/lib/micro/case/safe/flow.rb +0 -44
data/lib/micro/case.rb
CHANGED
@@ -1,39 +1,48 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'kind'
|
3
4
|
require 'micro/attributes'
|
4
|
-
|
5
|
+
|
6
|
+
require 'micro/case/version'
|
5
7
|
|
6
8
|
module Micro
|
7
9
|
class Case
|
8
|
-
require 'micro/case/
|
10
|
+
require 'micro/case/utils'
|
9
11
|
require 'micro/case/result'
|
10
12
|
require 'micro/case/error'
|
11
13
|
require 'micro/case/safe'
|
12
14
|
require 'micro/case/strict'
|
13
|
-
|
14
|
-
require 'micro/
|
15
|
-
require 'micro/case/safe/flow'
|
15
|
+
|
16
|
+
require 'micro/cases'
|
16
17
|
|
17
18
|
include Micro::Attributes.without(:strict_initialize)
|
18
19
|
|
20
|
+
def self.call(options = {})
|
21
|
+
new(options).call
|
22
|
+
end
|
23
|
+
|
19
24
|
def self.to_proc
|
20
25
|
Proc.new { |arg| call(arg) }
|
21
26
|
end
|
22
27
|
|
23
|
-
def self.
|
24
|
-
|
28
|
+
def self.call!
|
29
|
+
self
|
25
30
|
end
|
26
31
|
|
27
|
-
def self
|
28
|
-
|
32
|
+
def self.flow(*args)
|
33
|
+
@__flow_use_cases = args
|
29
34
|
end
|
30
35
|
|
31
|
-
def self
|
32
|
-
|
33
|
-
|
36
|
+
def self.inherited(subclass)
|
37
|
+
subclass.attributes(self.attributes_data({}))
|
38
|
+
subclass.extend ::Micro::Attributes.const_get('Macros::ForSubclasses'.freeze)
|
34
39
|
|
35
|
-
|
36
|
-
|
40
|
+
if self.send(:__flow_use_cases) && !subclass.name.to_s.end_with?(FLOW_STEP)
|
41
|
+
raise "Wooo, you can't do this! Inherits from a use case which has an inner flow violates "\
|
42
|
+
"one of the project principles: Solve complex business logic, by allowing the composition of use cases. "\
|
43
|
+
"Instead of doing this, declare a new class/constant with the steps needed.\n\n"\
|
44
|
+
"Related issue: https://github.com/serradura/u-case/issues/19\n"
|
45
|
+
end
|
37
46
|
end
|
38
47
|
|
39
48
|
def self.__new__(result, arg)
|
@@ -42,35 +51,19 @@ module Micro
|
|
42
51
|
instance
|
43
52
|
end
|
44
53
|
|
45
|
-
def self.
|
46
|
-
|
47
|
-
|
48
|
-
const_set(:Flow_Step, Class.new(self) do
|
49
|
-
private def __call
|
50
|
-
__call_use_case
|
51
|
-
end
|
52
|
-
end)
|
53
|
-
end
|
54
|
+
def self.__call_and_set_transition__(result, arg)
|
55
|
+
input =
|
56
|
+
arg.is_a?(Hash) ? result.__set_transitions_accessible_attributes__(arg) : arg
|
54
57
|
|
55
|
-
|
56
|
-
self
|
58
|
+
__new__(result, input).call
|
57
59
|
end
|
58
60
|
|
59
|
-
def self.
|
60
|
-
Flow
|
61
|
+
def self.__flow_builder
|
62
|
+
Cases::Flow
|
61
63
|
end
|
62
64
|
|
63
65
|
def self.__flow_get
|
64
|
-
@__flow
|
65
|
-
end
|
66
|
-
|
67
|
-
private_class_method def self.__flow_use_cases_set(args)
|
68
|
-
@__flow_use_cases = args
|
69
|
-
end
|
70
|
-
|
71
|
-
private_class_method def self.__flow_use_cases_get
|
72
|
-
Array(@__flow_use_cases)
|
73
|
-
.map { |use_case| use_case == self ? self.__call! : use_case }
|
66
|
+
return @__flow if defined?(@__flow)
|
74
67
|
end
|
75
68
|
|
76
69
|
private_class_method def self.__flow_set(args)
|
@@ -80,15 +73,30 @@ module Micro
|
|
80
73
|
|
81
74
|
self.class_eval('def use_cases; self.class.use_cases; end')
|
82
75
|
|
83
|
-
@__flow =
|
76
|
+
@__flow = __flow_builder.build(args)
|
84
77
|
end
|
85
78
|
|
86
|
-
|
87
|
-
|
79
|
+
FLOW_STEP = 'Flow_Step'.freeze
|
80
|
+
|
81
|
+
private_constant :FLOW_STEP
|
82
|
+
|
83
|
+
def self.__call!
|
84
|
+
return const_get(FLOW_STEP) if const_defined?(FLOW_STEP, false)
|
85
|
+
|
86
|
+
class_eval("class #{FLOW_STEP} < #{self.name}; private def __call; __call_use_case; end; end")
|
88
87
|
end
|
89
88
|
|
90
|
-
def self.
|
91
|
-
|
89
|
+
private_class_method def self.__flow_use_cases
|
90
|
+
return @__flow_use_cases if defined?(@__flow_use_cases)
|
91
|
+
end
|
92
|
+
|
93
|
+
private_class_method def self.__flow_use_cases_get
|
94
|
+
Array(__flow_use_cases)
|
95
|
+
.map { |use_case| use_case == self ? self.__call! : use_case }
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.__flow_set!
|
99
|
+
__flow_set(__flow_use_cases_get) if !__flow_get && __flow_use_cases
|
92
100
|
end
|
93
101
|
|
94
102
|
def initialize(input)
|
@@ -105,7 +113,7 @@ module Micro
|
|
105
113
|
|
106
114
|
def __set_result__(result)
|
107
115
|
raise Error::InvalidResultInstance unless result.is_a?(Result)
|
108
|
-
raise Error::ResultIsAlreadyDefined if @__result
|
116
|
+
raise Error::ResultIsAlreadyDefined if defined?(@__result)
|
109
117
|
|
110
118
|
@__result = result
|
111
119
|
end
|
@@ -142,29 +150,36 @@ module Micro
|
|
142
150
|
self.class.__flow_get.call(@__input)
|
143
151
|
end
|
144
152
|
|
145
|
-
def Success(
|
146
|
-
value
|
153
|
+
def Success(type = :ok, result: nil)
|
154
|
+
value = result || type
|
147
155
|
|
148
|
-
|
156
|
+
__get_result(true, value, type)
|
149
157
|
end
|
150
158
|
|
151
|
-
|
152
|
-
|
153
|
-
|
159
|
+
MapFailureType = -> (value, type) do
|
160
|
+
return type if type != :error
|
161
|
+
return value if value.is_a?(Symbol)
|
162
|
+
return :exception if value.is_a?(Exception)
|
154
163
|
|
155
|
-
|
164
|
+
type
|
156
165
|
end
|
157
166
|
|
158
|
-
def
|
159
|
-
|
167
|
+
def Failure(type = :error, result: nil)
|
168
|
+
value = result || type
|
169
|
+
|
170
|
+
type = MapFailureType.call(value, type)
|
171
|
+
|
172
|
+
__get_result(false, value, type)
|
160
173
|
end
|
161
174
|
|
162
|
-
def
|
163
|
-
|
164
|
-
|
165
|
-
return :exception if arg.is_a?(Exception)
|
175
|
+
def __result__
|
176
|
+
@__result ||= Result.new
|
177
|
+
end
|
166
178
|
|
167
|
-
|
179
|
+
def __get_result(is_success, value, type)
|
180
|
+
__result__.__set__(is_success, value, type, self)
|
168
181
|
end
|
182
|
+
|
183
|
+
private_constant :MapFailureType
|
169
184
|
end
|
170
185
|
end
|
data/lib/micro/case/error.rb
CHANGED
@@ -17,6 +17,21 @@ module Micro
|
|
17
17
|
def initialize; super('type must be a Symbol'.freeze); end
|
18
18
|
end
|
19
19
|
|
20
|
+
class InvalidResultData < TypeError
|
21
|
+
end
|
22
|
+
|
23
|
+
class InvalidSuccessResult < InvalidResultData
|
24
|
+
def initialize(object)
|
25
|
+
super("Success(result: #{object.inspect}) must be a Hash or Symbol")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class InvalidFailureResult < InvalidResultData
|
30
|
+
def initialize(object)
|
31
|
+
super("Failure(result: #{object.inspect}) must be a Hash, Symbol or an Exception")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
20
35
|
class InvalidResultInstance < ArgumentError
|
21
36
|
def initialize; super('argument must be an instance of Micro::Case::Result'.freeze); end
|
22
37
|
end
|
@@ -25,26 +40,16 @@ module Micro
|
|
25
40
|
def initialize; super('use case must be a kind or an instance of Micro::Case'.freeze); end
|
26
41
|
end
|
27
42
|
|
28
|
-
class InvalidUseCases < ArgumentError
|
29
|
-
def initialize; super('argument must be a collection of `Micro::Case` classes'.freeze); end
|
30
|
-
end
|
31
|
-
|
32
43
|
class InvalidInvocationOfTheThenMethod < StandardError
|
33
44
|
def initialize; super('Invalid invocation of the Micro::Case::Result#then method'); end
|
34
45
|
end
|
35
46
|
|
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
47
|
class InvalidAccessToTheUseCaseObject < StandardError
|
41
48
|
def initialize; super('only a failure result can access its use case object'.freeze); end
|
42
49
|
end
|
43
50
|
|
44
|
-
|
45
|
-
|
46
|
-
exception.is_a?(Error::UnexpectedResult) || exception.is_a?(ArgumentError)
|
47
|
-
end
|
51
|
+
def self.by_wrong_usage?(exception)
|
52
|
+
exception.is_a?(InvalidResultData) || exception.is_a?(Error::UnexpectedResult) || exception.is_a?(ArgumentError)
|
48
53
|
end
|
49
54
|
end
|
50
55
|
end
|
data/lib/micro/case/result.rb
CHANGED
@@ -1,29 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'set'
|
4
|
+
|
3
5
|
module Micro
|
4
6
|
class Case
|
5
7
|
class Result
|
6
|
-
|
7
|
-
attr_reader :value, :type
|
8
|
+
Kind::Types.add(self)
|
8
9
|
|
9
|
-
|
10
|
-
@value, @type = value, type
|
11
|
-
end
|
10
|
+
@@transition_tracking_disabled = false
|
12
11
|
|
13
|
-
|
12
|
+
def self.disable_transition_tracking
|
13
|
+
@@transition_tracking_disabled = true
|
14
14
|
end
|
15
15
|
|
16
|
-
|
16
|
+
attr_reader :type, :data
|
17
17
|
|
18
|
-
|
18
|
+
alias_method :value, :data
|
19
19
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
20
|
+
def initialize
|
21
|
+
@__transitions__ = []
|
22
|
+
@__transitions_accessible_attributes__ = {}
|
23
|
+
end
|
23
24
|
|
24
|
-
|
25
|
+
def to_ary
|
26
|
+
[data, type]
|
27
|
+
end
|
25
28
|
|
26
|
-
|
29
|
+
def [](key)
|
30
|
+
data[key]
|
31
|
+
end
|
32
|
+
|
33
|
+
def values_at(*keys)
|
34
|
+
data.values_at(*keys)
|
27
35
|
end
|
28
36
|
|
29
37
|
def success?
|
@@ -41,18 +49,32 @@ module Micro
|
|
41
49
|
end
|
42
50
|
|
43
51
|
def on_success(expected_type = nil)
|
44
|
-
|
52
|
+
yield(data) if success_type?(expected_type)
|
53
|
+
|
54
|
+
self
|
45
55
|
end
|
46
56
|
|
47
57
|
def on_failure(expected_type = nil)
|
48
58
|
return self unless failure_type?(expected_type)
|
49
59
|
|
50
|
-
|
60
|
+
hook_data = expected_type.nil? ? self : data
|
61
|
+
|
62
|
+
yield(hook_data, @use_case)
|
63
|
+
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
def on_exception(expected_exception = nil)
|
68
|
+
return self unless failure_type?(:exception)
|
51
69
|
|
52
|
-
|
70
|
+
if !expected_exception || (Kind.is(Exception, expected_exception) && data.fetch(:exception).is_a?(expected_exception))
|
71
|
+
yield(data, @use_case)
|
72
|
+
end
|
73
|
+
|
74
|
+
self
|
53
75
|
end
|
54
76
|
|
55
|
-
def then(arg = nil, &block)
|
77
|
+
def then(arg = nil, attributes = nil, &block)
|
56
78
|
can_yield_self = respond_to?(:yield_self)
|
57
79
|
|
58
80
|
if block
|
@@ -67,10 +89,45 @@ module Micro
|
|
67
89
|
|
68
90
|
return self if failure?
|
69
91
|
|
70
|
-
|
92
|
+
input = attributes.is_a?(Hash) ? self.data.merge(attributes) : self.data
|
93
|
+
|
94
|
+
arg.__call_and_set_transition__(self, input)
|
71
95
|
end
|
72
96
|
end
|
73
97
|
|
98
|
+
def transitions
|
99
|
+
@__transitions__.clone
|
100
|
+
end
|
101
|
+
|
102
|
+
FetchData = -> (data, is_success) do
|
103
|
+
return data if data.is_a?(Hash)
|
104
|
+
return { data => true } if data.is_a?(Symbol)
|
105
|
+
return { exception: data } if data.is_a?(Exception)
|
106
|
+
|
107
|
+
err = is_success ? :InvalidSuccessResult : :InvalidFailureResult
|
108
|
+
|
109
|
+
raise Micro::Case::Error.const_get(err), data
|
110
|
+
end
|
111
|
+
|
112
|
+
def __set__(is_success, data, type, use_case)
|
113
|
+
raise Error::InvalidResultType unless type.is_a?(Symbol)
|
114
|
+
raise Error::InvalidUseCase if !is_a_use_case?(use_case)
|
115
|
+
|
116
|
+
@success, @type, @use_case = is_success, type, use_case
|
117
|
+
|
118
|
+
@data = FetchData.call(data, is_success)
|
119
|
+
|
120
|
+
__set_transition__ unless @@transition_tracking_disabled
|
121
|
+
|
122
|
+
self
|
123
|
+
end
|
124
|
+
|
125
|
+
def __set_transitions_accessible_attributes__(attributes_data)
|
126
|
+
return attributes_data if @@transition_tracking_disabled
|
127
|
+
|
128
|
+
__set_transitions_accessible_attributes__!(attributes_data)
|
129
|
+
end
|
130
|
+
|
74
131
|
private
|
75
132
|
|
76
133
|
def success_type?(expected_type)
|
@@ -84,6 +141,34 @@ module Micro
|
|
84
141
|
def is_a_use_case?(arg)
|
85
142
|
(arg.is_a?(Class) && arg < ::Micro::Case) || arg.is_a?(::Micro::Case)
|
86
143
|
end
|
144
|
+
|
145
|
+
def __set_transitions_accessible_attributes__!(attributes_data)
|
146
|
+
attributes = Utils.symbolize_hash_keys(attributes_data)
|
147
|
+
|
148
|
+
__update_transitions_accessible_attributes__(attributes)
|
149
|
+
end
|
150
|
+
|
151
|
+
def __update_transitions_accessible_attributes__(attributes)
|
152
|
+
@__transitions_accessible_attributes__.merge!(attributes)
|
153
|
+
@__transitions_accessible_attributes__
|
154
|
+
end
|
155
|
+
|
156
|
+
def __set_transition__
|
157
|
+
use_case_class = @use_case.class
|
158
|
+
use_case_attributes = Utils.symbolize_hash_keys(@use_case.attributes)
|
159
|
+
|
160
|
+
__update_transitions_accessible_attributes__(use_case_attributes)
|
161
|
+
|
162
|
+
result = @success ? :success : :failure
|
163
|
+
|
164
|
+
@__transitions__ << {
|
165
|
+
use_case: { class: use_case_class, attributes: use_case_attributes },
|
166
|
+
result => { type: @type, result: data },
|
167
|
+
accessible_attributes: @__transitions_accessible_attributes__.keys
|
168
|
+
}
|
169
|
+
end
|
170
|
+
|
171
|
+
private_constant :FetchData
|
87
172
|
end
|
88
173
|
end
|
89
174
|
end
|
data/lib/micro/case/safe.rb
CHANGED
@@ -3,12 +3,16 @@
|
|
3
3
|
module Micro
|
4
4
|
class Case
|
5
5
|
class Safe < ::Micro::Case
|
6
|
+
def self.__flow_builder
|
7
|
+
Cases::Safe::Flow
|
8
|
+
end
|
9
|
+
|
6
10
|
def call
|
7
11
|
__call
|
8
12
|
rescue => exception
|
9
|
-
raise exception if Error
|
13
|
+
raise exception if Error.by_wrong_usage?(exception)
|
10
14
|
|
11
|
-
Failure(exception)
|
15
|
+
Failure(result: exception)
|
12
16
|
end
|
13
17
|
end
|
14
18
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Micro
|
4
|
+
class Case
|
5
|
+
module Utils
|
6
|
+
def self.symbolize_hash_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
|