u-case 2.6.0 → 3.0.0.rc5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.sh +3 -2
- data/Gemfile +11 -2
- data/README.md +472 -441
- data/README.pt-BR.md +1390 -0
- data/lib/micro/case.rb +96 -77
- data/lib/micro/case/config.rb +23 -0
- data/lib/micro/case/error.rb +24 -18
- data/lib/micro/case/result.rb +94 -55
- data/lib/micro/case/safe.rb +8 -4
- data/lib/micro/case/utils.rb +7 -0
- data/lib/micro/case/version.rb +1 -1
- data/lib/micro/case/with_activemodel_validation.rb +4 -2
- data/lib/micro/cases.rb +16 -0
- data/lib/micro/cases/flow.rb +86 -0
- data/lib/micro/cases/safe/flow.rb +18 -0
- data/lib/u-case/with_activemodel_validation.rb +0 -2
- data/u-case.gemspec +3 -3
- metadata +19 -12
- data/lib/micro/case/flow.rb +0 -48
- data/lib/micro/case/flow/reducer.rb +0 -104
- data/lib/micro/case/safe/flow.rb +0 -44
- data/lib/u-case/with_validation.rb +0 -6
data/lib/micro/case.rb
CHANGED
@@ -3,65 +3,58 @@
|
|
3
3
|
require 'kind'
|
4
4
|
require 'micro/attributes'
|
5
5
|
|
6
|
+
require 'micro/case/version'
|
7
|
+
|
6
8
|
module Micro
|
7
9
|
class Case
|
8
|
-
require 'micro/case/version'
|
9
10
|
require 'micro/case/utils'
|
10
11
|
require 'micro/case/result'
|
11
12
|
require 'micro/case/error'
|
12
13
|
require 'micro/case/safe'
|
13
14
|
require 'micro/case/strict'
|
14
|
-
require 'micro/case/
|
15
|
-
|
16
|
-
require 'micro/
|
15
|
+
require 'micro/case/config'
|
16
|
+
|
17
|
+
require 'micro/cases'
|
17
18
|
|
18
19
|
include Micro::Attributes.without(:strict_initialize)
|
19
20
|
|
20
|
-
def self.
|
21
|
-
|
21
|
+
def self.call(options = Kind::Empty::HASH)
|
22
|
+
new(options).__call__
|
22
23
|
end
|
23
24
|
|
24
|
-
def self.
|
25
|
-
|
26
|
-
end
|
25
|
+
def self.then(use_case = nil, &block)
|
26
|
+
can_yield_self = respond_to?(:yield_self)
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
if block
|
29
|
+
raise Error::InvalidInvocationOfTheThenMethod if use_case
|
30
|
+
raise NotImplementedError if !can_yield_self
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
yield_self(&block)
|
33
|
+
else
|
34
|
+
return yield_self if !use_case && can_yield_self
|
35
35
|
|
36
|
-
|
37
|
-
|
36
|
+
self.call.then(use_case)
|
37
|
+
end
|
38
38
|
end
|
39
39
|
|
40
|
-
def self.
|
41
|
-
|
42
|
-
instance.__set_result__(result)
|
43
|
-
instance
|
40
|
+
def self.to_proc
|
41
|
+
Proc.new { |arg| call(arg) }
|
44
42
|
end
|
45
43
|
|
46
|
-
def self.
|
47
|
-
|
48
|
-
arg.is_a?(Hash) ? result.__set_transitions_accessible_attributes__(arg) : arg
|
49
|
-
|
50
|
-
__new__(result, input).call
|
44
|
+
def self.flow(*args)
|
45
|
+
@__flow_use_cases = args
|
51
46
|
end
|
52
47
|
|
53
|
-
|
54
|
-
|
55
|
-
private_constant :FLOW_STEP
|
48
|
+
class << self
|
49
|
+
alias __call__ call
|
56
50
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
class_eval("class #{FLOW_STEP} < #{self.name}; private def __call; __call_use_case; end; end")
|
61
|
-
end
|
51
|
+
def config
|
52
|
+
yield(Config.instance)
|
53
|
+
end
|
62
54
|
|
63
|
-
|
64
|
-
|
55
|
+
def call!
|
56
|
+
self
|
57
|
+
end
|
65
58
|
end
|
66
59
|
|
67
60
|
def self.inherited(subclass)
|
@@ -76,43 +69,58 @@ module Micro
|
|
76
69
|
end
|
77
70
|
end
|
78
71
|
|
79
|
-
def self.
|
80
|
-
|
72
|
+
def self.__new__(result, arg)
|
73
|
+
instance = new(arg)
|
74
|
+
instance.__set_result__(result)
|
75
|
+
instance
|
81
76
|
end
|
82
77
|
|
83
|
-
def self.
|
84
|
-
|
85
|
-
|
78
|
+
def self.__call_and_set_transition__(result, arg)
|
79
|
+
input =
|
80
|
+
arg.is_a?(Hash) ? result.__set_transitions_accessible_attributes__(arg) : arg
|
86
81
|
|
87
|
-
|
88
|
-
return @__flow_use_cases if defined?(@__flow_use_cases)
|
82
|
+
__new__(result, input).__call__
|
89
83
|
end
|
90
84
|
|
91
|
-
|
92
|
-
|
93
|
-
.map { |use_case| use_case == self ? self.__call! : use_case }
|
85
|
+
def self.__flow_builder__
|
86
|
+
Cases::Flow
|
94
87
|
end
|
95
88
|
|
96
|
-
|
97
|
-
@
|
89
|
+
def self.__flow_get__
|
90
|
+
return @__flow if defined?(@__flow)
|
98
91
|
end
|
99
92
|
|
100
93
|
private_class_method def self.__flow_set(args)
|
101
|
-
return if
|
94
|
+
return if __flow_get__
|
102
95
|
|
103
|
-
def self.use_cases;
|
96
|
+
def self.use_cases; __flow_get__.use_cases; end
|
104
97
|
|
105
98
|
self.class_eval('def use_cases; self.class.use_cases; end')
|
106
99
|
|
107
|
-
@__flow =
|
100
|
+
@__flow = __flow_builder__.build(args)
|
108
101
|
end
|
109
102
|
|
110
|
-
|
111
|
-
|
103
|
+
FLOW_STEP = 'Self'.freeze
|
104
|
+
|
105
|
+
private_constant :FLOW_STEP
|
106
|
+
|
107
|
+
def self.__call__!
|
108
|
+
return const_get(FLOW_STEP) if const_defined?(FLOW_STEP, false)
|
109
|
+
|
110
|
+
class_eval("class #{FLOW_STEP} < #{self.name}; private def __call; __call_use_case; end; end")
|
112
111
|
end
|
113
112
|
|
114
|
-
def self.
|
115
|
-
|
113
|
+
private_class_method def self.__flow_use_cases
|
114
|
+
return @__flow_use_cases if defined?(@__flow_use_cases)
|
115
|
+
end
|
116
|
+
|
117
|
+
private_class_method def self.__flow_use_cases_get
|
118
|
+
Array(__flow_use_cases)
|
119
|
+
.map { |use_case| use_case == self ? self.__call__! : use_case }
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.__flow_set__!
|
123
|
+
__flow_set(__flow_use_cases_get) if !__flow_get__ && __flow_use_cases
|
116
124
|
end
|
117
125
|
|
118
126
|
def initialize(input)
|
@@ -123,8 +131,8 @@ module Micro
|
|
123
131
|
raise NotImplementedError
|
124
132
|
end
|
125
133
|
|
126
|
-
def
|
127
|
-
__call
|
134
|
+
def __call__
|
135
|
+
__call!
|
128
136
|
end
|
129
137
|
|
130
138
|
def __set_result__(result)
|
@@ -136,15 +144,19 @@ module Micro
|
|
136
144
|
|
137
145
|
private
|
138
146
|
|
147
|
+
# This method was reserved for a new feature
|
148
|
+
def call
|
149
|
+
end
|
150
|
+
|
139
151
|
def __setup_use_case(input)
|
140
|
-
self.class.
|
152
|
+
self.class.__flow_set__!
|
141
153
|
|
142
154
|
@__input = input
|
143
155
|
|
144
156
|
self.attributes = input
|
145
157
|
end
|
146
158
|
|
147
|
-
def __call
|
159
|
+
def __call!
|
148
160
|
return __call_use_case_flow if __call_use_case_flow?
|
149
161
|
|
150
162
|
__call_use_case
|
@@ -155,44 +167,51 @@ module Micro
|
|
155
167
|
|
156
168
|
return result if result.is_a?(Result)
|
157
169
|
|
158
|
-
raise Error::UnexpectedResult.new(self.class)
|
170
|
+
raise Error::UnexpectedResult.new("#{self.class.name}#call!")
|
159
171
|
end
|
160
172
|
|
161
173
|
def __call_use_case_flow?
|
162
|
-
self.class.
|
174
|
+
self.class.__flow_get__
|
163
175
|
end
|
164
176
|
|
165
177
|
def __call_use_case_flow
|
166
|
-
self.class.
|
178
|
+
self.class.__flow_get__.call(@__input)
|
167
179
|
end
|
168
180
|
|
169
|
-
def Success(
|
170
|
-
value
|
181
|
+
def Success(type = :ok, result: nil)
|
182
|
+
value = result || type
|
171
183
|
|
172
|
-
|
184
|
+
__get_result(true, value, type)
|
173
185
|
end
|
174
186
|
|
175
|
-
|
176
|
-
|
177
|
-
|
187
|
+
MapFailureType = -> (value, type) do
|
188
|
+
return type if type != :error
|
189
|
+
return value if value.is_a?(Symbol)
|
190
|
+
return :exception if value.is_a?(Exception)
|
178
191
|
|
179
|
-
|
192
|
+
type
|
180
193
|
end
|
181
194
|
|
182
|
-
def
|
183
|
-
|
184
|
-
return arg if arg.is_a?(Symbol)
|
185
|
-
return :exception if arg.is_a?(Exception)
|
195
|
+
def Failure(type = :error, result: nil)
|
196
|
+
value = result || type
|
186
197
|
|
187
|
-
type
|
198
|
+
type = MapFailureType.call(value, type)
|
199
|
+
|
200
|
+
__get_result(false, value, type)
|
188
201
|
end
|
189
202
|
|
190
|
-
def
|
203
|
+
def __result
|
191
204
|
@__result ||= Result.new
|
192
205
|
end
|
193
206
|
|
194
|
-
def
|
195
|
-
|
207
|
+
def __get_result(is_success, value, type)
|
208
|
+
__result.__set__(is_success, value, type, self)
|
196
209
|
end
|
210
|
+
|
211
|
+
private_constant :MapFailureType
|
212
|
+
end
|
213
|
+
|
214
|
+
def self.case_or_flow?(arg)
|
215
|
+
(arg.is_a?(Class) && arg < Case) || arg.is_a?(Cases::Flow)
|
197
216
|
end
|
198
217
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
module Micro
|
6
|
+
class Case
|
7
|
+
class Config
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
def enable_activemodel_validation=(value)
|
11
|
+
return unless Kind::Of::Boolean(value)
|
12
|
+
|
13
|
+
require 'micro/case/with_activemodel_validation'
|
14
|
+
end
|
15
|
+
|
16
|
+
def enable_transitions=(value)
|
17
|
+
Micro::Case::Result.class_variable_set(
|
18
|
+
:@@transition_tracking_disabled, !Kind::Of::Boolean(value)
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/micro/case/error.rb
CHANGED
@@ -4,9 +4,11 @@ module Micro
|
|
4
4
|
class Case
|
5
5
|
module Error
|
6
6
|
class UnexpectedResult < TypeError
|
7
|
-
MESSAGE = '
|
7
|
+
MESSAGE = 'must return an instance of Micro::Case::Result'.freeze
|
8
8
|
|
9
|
-
def initialize(
|
9
|
+
def initialize(context)
|
10
|
+
super("#{context} #{MESSAGE}")
|
11
|
+
end
|
10
12
|
end
|
11
13
|
|
12
14
|
class ResultIsAlreadyDefined < ArgumentError
|
@@ -17,6 +19,24 @@ module Micro
|
|
17
19
|
def initialize; super('type must be a Symbol'.freeze); end
|
18
20
|
end
|
19
21
|
|
22
|
+
class InvalidResult < TypeError
|
23
|
+
def initialize(is_success, type, use_case)
|
24
|
+
base =
|
25
|
+
"The result returned from #{use_case.class.name}#call! must be a Hash."
|
26
|
+
|
27
|
+
result = is_success ? 'Success'.freeze : 'Failure'.freeze
|
28
|
+
|
29
|
+
example =
|
30
|
+
if type === :ok || type === :error || type === :exception
|
31
|
+
"#{result}(result: { key: 'value' })"
|
32
|
+
else
|
33
|
+
"#{result}(:#{type}, result: { key: 'value' })"
|
34
|
+
end
|
35
|
+
|
36
|
+
super("#{base}\n\nExample:\n #{example}")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
20
40
|
class InvalidResultInstance < ArgumentError
|
21
41
|
def initialize; super('argument must be an instance of Micro::Case::Result'.freeze); end
|
22
42
|
end
|
@@ -25,26 +45,12 @@ module Micro
|
|
25
45
|
def initialize; super('use case must be a kind or an instance of Micro::Case'.freeze); end
|
26
46
|
end
|
27
47
|
|
28
|
-
class InvalidUseCases < ArgumentError
|
29
|
-
def initialize; super('argument must be a collection of `Micro::Case` classes'.freeze); end
|
30
|
-
end
|
31
|
-
|
32
48
|
class InvalidInvocationOfTheThenMethod < StandardError
|
33
49
|
def initialize; super('Invalid invocation of the Micro::Case::Result#then method'); end
|
34
50
|
end
|
35
51
|
|
36
|
-
|
37
|
-
|
38
|
-
end
|
39
|
-
|
40
|
-
class InvalidAccessToTheUseCaseObject < StandardError
|
41
|
-
def initialize; super('only a failure result can access its use case object'.freeze); end
|
42
|
-
end
|
43
|
-
|
44
|
-
module ByWrongUsage
|
45
|
-
def self.check(exception)
|
46
|
-
exception.is_a?(Error::UnexpectedResult) || exception.is_a?(ArgumentError)
|
47
|
-
end
|
52
|
+
def self.by_wrong_usage?(exception)
|
53
|
+
exception.is_a?(InvalidResult) || exception.is_a?(UnexpectedResult) || exception.is_a?(ArgumentError)
|
48
54
|
end
|
49
55
|
end
|
50
56
|
end
|
data/lib/micro/case/result.rb
CHANGED
@@ -9,38 +9,37 @@ module Micro
|
|
9
9
|
|
10
10
|
@@transition_tracking_disabled = false
|
11
11
|
|
12
|
-
|
13
|
-
@@transition_tracking_disabled = true
|
14
|
-
end
|
15
|
-
|
16
|
-
class Data
|
17
|
-
attr_reader :value, :type
|
18
|
-
|
19
|
-
def initialize(value, type)
|
20
|
-
@value, @type = value, type
|
21
|
-
end
|
22
|
-
|
23
|
-
def to_ary; [value, type]; end
|
24
|
-
end
|
12
|
+
attr_reader :type, :data, :use_case
|
25
13
|
|
26
|
-
|
27
|
-
|
28
|
-
attr_reader :value, :type
|
14
|
+
alias value data
|
29
15
|
|
30
16
|
def initialize
|
31
17
|
@__transitions__ = []
|
32
18
|
@__transitions_accessible_attributes__ = {}
|
33
19
|
end
|
34
20
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
21
|
+
def to_ary
|
22
|
+
[data, type]
|
23
|
+
end
|
24
|
+
|
25
|
+
def [](key)
|
26
|
+
data[key]
|
27
|
+
end
|
38
28
|
|
39
|
-
|
29
|
+
def values_at(*keys)
|
30
|
+
data.values_at(*keys)
|
31
|
+
end
|
40
32
|
|
41
|
-
|
33
|
+
def key?(key)
|
34
|
+
data.key?(key)
|
35
|
+
end
|
42
36
|
|
43
|
-
|
37
|
+
def value?(value)
|
38
|
+
data.value?(value)
|
39
|
+
end
|
40
|
+
|
41
|
+
def slice(*keys)
|
42
|
+
Utils.slice_hash(data, keys)
|
44
43
|
end
|
45
44
|
|
46
45
|
def success?
|
@@ -51,108 +50,148 @@ module Micro
|
|
51
50
|
!success?
|
52
51
|
end
|
53
52
|
|
54
|
-
def
|
55
|
-
return
|
53
|
+
def on_success(expected_type = nil)
|
54
|
+
return self unless __success_type?(expected_type)
|
56
55
|
|
57
|
-
|
58
|
-
end
|
56
|
+
hook_data = expected_type.nil? ? self : data
|
59
57
|
|
60
|
-
|
61
|
-
yield(value) if success_type?(expected_type)
|
58
|
+
yield(hook_data, @use_case)
|
62
59
|
|
63
60
|
self
|
64
61
|
end
|
65
62
|
|
66
63
|
def on_failure(expected_type = nil)
|
67
|
-
return self unless
|
64
|
+
return self unless __failure_type?(expected_type)
|
68
65
|
|
69
|
-
|
66
|
+
hook_data = expected_type.nil? ? self : data
|
70
67
|
|
71
|
-
yield(
|
68
|
+
yield(hook_data, @use_case)
|
72
69
|
|
73
70
|
self
|
74
71
|
end
|
75
72
|
|
76
73
|
def on_exception(expected_exception = nil)
|
77
|
-
return self unless
|
74
|
+
return self unless __failure_type?(:exception)
|
78
75
|
|
79
|
-
if !expected_exception || (Kind.is(Exception, expected_exception) &&
|
80
|
-
yield(
|
76
|
+
if !expected_exception || (Kind.is(Exception, expected_exception) && data.fetch(:exception).is_a?(expected_exception))
|
77
|
+
yield(data, @use_case)
|
81
78
|
end
|
82
79
|
|
83
80
|
self
|
84
81
|
end
|
85
82
|
|
86
|
-
def then(
|
83
|
+
def then(use_case = nil, attributes = nil, &block)
|
87
84
|
can_yield_self = respond_to?(:yield_self)
|
88
85
|
|
89
86
|
if block
|
90
|
-
raise Error::InvalidInvocationOfTheThenMethod if
|
87
|
+
raise Error::InvalidInvocationOfTheThenMethod if use_case
|
91
88
|
raise NotImplementedError if !can_yield_self
|
92
89
|
|
93
90
|
yield_self(&block)
|
94
91
|
else
|
95
|
-
return yield_self if !
|
92
|
+
return yield_self if !use_case && can_yield_self
|
93
|
+
|
94
|
+
if use_case.is_a?(Proc)
|
95
|
+
return failure? ? self : __call_proc(use_case, expected: 'then(-> {})'.freeze)
|
96
|
+
end
|
96
97
|
|
97
|
-
raise Error::InvalidInvocationOfTheThenMethod
|
98
|
+
raise Error::InvalidInvocationOfTheThenMethod unless ::Micro.case_or_flow?(use_case)
|
98
99
|
|
99
100
|
return self if failure?
|
100
101
|
|
101
|
-
input = attributes.is_a?(Hash) ? self.
|
102
|
+
input = attributes.is_a?(Hash) ? self.data.merge(attributes) : self.data
|
102
103
|
|
103
|
-
|
104
|
+
if use_case.is_a?(::Micro::Cases::Flow)
|
105
|
+
use_case.call!(input: input, result: self)
|
106
|
+
else
|
107
|
+
use_case.__call_and_set_transition__(self, input)
|
108
|
+
end
|
104
109
|
end
|
105
110
|
end
|
106
111
|
|
112
|
+
def |(arg)
|
113
|
+
return self if failure?
|
114
|
+
|
115
|
+
return __call_proc(arg, expected: '| -> {}'.freeze) if arg.is_a?(Proc)
|
116
|
+
|
117
|
+
raise Error::InvalidInvocationOfTheThenMethod unless ::Micro.case_or_flow?(arg)
|
118
|
+
|
119
|
+
failure? ? self : arg.__call_and_set_transition__(self, data)
|
120
|
+
end
|
121
|
+
|
107
122
|
def transitions
|
108
123
|
@__transitions__.clone
|
109
124
|
end
|
110
125
|
|
126
|
+
FetchData = -> (data) do
|
127
|
+
return data if data.is_a?(Hash)
|
128
|
+
return { data => true } if data.is_a?(Symbol)
|
129
|
+
|
130
|
+
{ exception: data } if data.is_a?(Exception)
|
131
|
+
end
|
132
|
+
|
133
|
+
def __set__(is_success, data, type, use_case)
|
134
|
+
raise Error::InvalidResultType unless type.is_a?(Symbol)
|
135
|
+
raise Error::InvalidUseCase unless use_case.is_a?(::Micro::Case)
|
136
|
+
|
137
|
+
@success, @type, @use_case = is_success, type, use_case
|
138
|
+
|
139
|
+
@data = FetchData.call(data)
|
140
|
+
|
141
|
+
raise Micro::Case::Error::InvalidResult.new(is_success, type, use_case) unless @data
|
142
|
+
|
143
|
+
__set_transition unless @@transition_tracking_disabled
|
144
|
+
|
145
|
+
self
|
146
|
+
end
|
147
|
+
|
111
148
|
def __set_transitions_accessible_attributes__(attributes_data)
|
112
149
|
return attributes_data if @@transition_tracking_disabled
|
113
150
|
|
114
|
-
|
151
|
+
attributes = Utils.symbolize_hash_keys(attributes_data)
|
152
|
+
|
153
|
+
__update_transitions_accessible_attributes(attributes)
|
115
154
|
end
|
116
155
|
|
117
156
|
private
|
118
157
|
|
119
|
-
def
|
120
|
-
|
158
|
+
def __call_proc(arg, expected:)
|
159
|
+
result = arg.arity.zero? ? arg.call : arg.call(data.clone)
|
121
160
|
|
122
|
-
|
123
|
-
end
|
161
|
+
return result if result.is_a?(Result)
|
124
162
|
|
125
|
-
|
126
|
-
@__transitions_accessible_attributes__.merge!(attributes)
|
127
|
-
@__transitions_accessible_attributes__
|
163
|
+
raise Error::UnexpectedResult.new("#{Result.name}##{expected}")
|
128
164
|
end
|
129
165
|
|
130
|
-
def
|
166
|
+
def __success_type?(expected_type)
|
131
167
|
success? && (expected_type.nil? || expected_type == type)
|
132
168
|
end
|
133
169
|
|
134
|
-
def
|
170
|
+
def __failure_type?(expected_type)
|
135
171
|
failure? && (expected_type.nil? || expected_type == type)
|
136
172
|
end
|
137
173
|
|
138
|
-
def
|
139
|
-
|
174
|
+
def __update_transitions_accessible_attributes(attributes)
|
175
|
+
@__transitions_accessible_attributes__.merge!(attributes)
|
176
|
+
@__transitions_accessible_attributes__
|
140
177
|
end
|
141
178
|
|
142
|
-
def
|
179
|
+
def __set_transition
|
143
180
|
use_case_class = @use_case.class
|
144
181
|
use_case_attributes = Utils.symbolize_hash_keys(@use_case.attributes)
|
145
182
|
|
146
|
-
|
183
|
+
__update_transitions_accessible_attributes(use_case_attributes)
|
147
184
|
|
148
185
|
result = @success ? :success : :failure
|
149
186
|
|
150
187
|
@__transitions__ << {
|
151
188
|
use_case: { class: use_case_class, attributes: use_case_attributes },
|
152
|
-
result => { type: @type,
|
189
|
+
result => { type: @type, result: data },
|
153
190
|
accessible_attributes: @__transitions_accessible_attributes__.keys
|
154
191
|
}
|
155
192
|
end
|
193
|
+
|
194
|
+
private_constant :FetchData
|
156
195
|
end
|
157
196
|
end
|
158
197
|
end
|