u-case 2.5.0 → 3.0.0.rc4
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 +3 -2
- data/Gemfile +10 -0
- data/README.md +405 -348
- data/lib/micro/case.rb +89 -82
- data/lib/micro/case/config.rb +23 -0
- data/lib/micro/case/error.rb +24 -18
- data/lib/micro/case/result.rb +90 -64
- data/lib/micro/case/safe.rb +8 -4
- 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 +92 -0
- data/lib/micro/cases/safe/flow.rb +18 -0
- data/lib/u-case/with_activemodel_validation.rb +0 -2
- data/u-case.gemspec +1 -1
- metadata +16 -10
- 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,66 +3,43 @@
|
|
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
|
-
require 'micro/case/flow'
|
16
|
-
require 'micro/case/safe/flow'
|
17
|
-
|
18
|
-
include Micro::Attributes.without(:strict_initialize)
|
19
|
-
|
20
|
-
def self.to_proc
|
21
|
-
Proc.new { |arg| call(arg) }
|
22
|
-
end
|
15
|
+
require 'micro/case/config'
|
23
16
|
|
24
|
-
|
25
|
-
Flow::Reducer.build(Array(args))
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.>>(use_case)
|
29
|
-
Flow([self, use_case])
|
30
|
-
end
|
17
|
+
require 'micro/cases'
|
31
18
|
|
32
|
-
|
33
|
-
Safe::Flow([self, use_case])
|
34
|
-
end
|
19
|
+
include Micro::Attributes.without(:strict_initialize)
|
35
20
|
|
36
21
|
def self.call(options = {})
|
37
|
-
new(options).
|
22
|
+
new(options).__call__
|
38
23
|
end
|
39
24
|
|
40
|
-
def self.
|
41
|
-
|
42
|
-
instance.__set_result__(result)
|
43
|
-
instance
|
25
|
+
def self.to_proc
|
26
|
+
Proc.new { |arg| call(arg) }
|
44
27
|
end
|
45
28
|
|
46
|
-
def self.
|
47
|
-
|
48
|
-
result.__set_transitions_accessible_attributes__(arg.keys)
|
49
|
-
end
|
50
|
-
|
51
|
-
__new__(result, arg).call
|
29
|
+
def self.flow(*args)
|
30
|
+
@__flow_use_cases = args
|
52
31
|
end
|
53
32
|
|
54
|
-
|
55
|
-
|
56
|
-
private_constant :FLOW_STEP
|
57
|
-
|
58
|
-
def self.__call!
|
59
|
-
return const_get(FLOW_STEP) if const_defined?(FLOW_STEP, false)
|
33
|
+
class << self
|
34
|
+
alias __call__ call
|
60
35
|
|
61
|
-
|
62
|
-
|
36
|
+
def config
|
37
|
+
yield(Config.instance)
|
38
|
+
end
|
63
39
|
|
64
|
-
|
65
|
-
|
40
|
+
def call!
|
41
|
+
self
|
42
|
+
end
|
66
43
|
end
|
67
44
|
|
68
45
|
def self.inherited(subclass)
|
@@ -77,43 +54,58 @@ module Micro
|
|
77
54
|
end
|
78
55
|
end
|
79
56
|
|
80
|
-
def self.
|
81
|
-
|
57
|
+
def self.__new__(result, arg)
|
58
|
+
instance = new(arg)
|
59
|
+
instance.__set_result__(result)
|
60
|
+
instance
|
82
61
|
end
|
83
62
|
|
84
|
-
def self.
|
85
|
-
|
86
|
-
|
63
|
+
def self.__call_and_set_transition__(result, arg)
|
64
|
+
input =
|
65
|
+
arg.is_a?(Hash) ? result.__set_transitions_accessible_attributes__(arg) : arg
|
87
66
|
|
88
|
-
|
89
|
-
return @__flow_use_cases if defined?(@__flow_use_cases)
|
67
|
+
__new__(result, input).__call__
|
90
68
|
end
|
91
69
|
|
92
|
-
|
93
|
-
|
94
|
-
.map { |use_case| use_case == self ? self.__call! : use_case }
|
70
|
+
def self.__flow_builder__
|
71
|
+
Cases::Flow
|
95
72
|
end
|
96
73
|
|
97
|
-
|
98
|
-
@
|
74
|
+
def self.__flow_get__
|
75
|
+
return @__flow if defined?(@__flow)
|
99
76
|
end
|
100
77
|
|
101
78
|
private_class_method def self.__flow_set(args)
|
102
|
-
return if
|
79
|
+
return if __flow_get__
|
103
80
|
|
104
|
-
def self.use_cases;
|
81
|
+
def self.use_cases; __flow_get__.use_cases; end
|
105
82
|
|
106
83
|
self.class_eval('def use_cases; self.class.use_cases; end')
|
107
84
|
|
108
|
-
@__flow =
|
85
|
+
@__flow = __flow_builder__.build(args)
|
109
86
|
end
|
110
87
|
|
111
|
-
|
112
|
-
|
88
|
+
FLOW_STEP = 'Flow_Step'.freeze
|
89
|
+
|
90
|
+
private_constant :FLOW_STEP
|
91
|
+
|
92
|
+
def self.__call__!
|
93
|
+
return const_get(FLOW_STEP) if const_defined?(FLOW_STEP, false)
|
94
|
+
|
95
|
+
class_eval("class #{FLOW_STEP} < #{self.name}; private def __call; __call_use_case; end; end")
|
113
96
|
end
|
114
97
|
|
115
|
-
def self.
|
116
|
-
|
98
|
+
private_class_method def self.__flow_use_cases
|
99
|
+
return @__flow_use_cases if defined?(@__flow_use_cases)
|
100
|
+
end
|
101
|
+
|
102
|
+
private_class_method def self.__flow_use_cases_get
|
103
|
+
Array(__flow_use_cases)
|
104
|
+
.map { |use_case| use_case == self ? self.__call__! : use_case }
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.__flow_set__!
|
108
|
+
__flow_set(__flow_use_cases_get) if !__flow_get__ && __flow_use_cases
|
117
109
|
end
|
118
110
|
|
119
111
|
def initialize(input)
|
@@ -124,8 +116,8 @@ module Micro
|
|
124
116
|
raise NotImplementedError
|
125
117
|
end
|
126
118
|
|
127
|
-
def
|
128
|
-
__call
|
119
|
+
def __call__
|
120
|
+
__call!
|
129
121
|
end
|
130
122
|
|
131
123
|
def __set_result__(result)
|
@@ -137,15 +129,19 @@ module Micro
|
|
137
129
|
|
138
130
|
private
|
139
131
|
|
132
|
+
# This method was reserved for a new feature
|
133
|
+
def call
|
134
|
+
end
|
135
|
+
|
140
136
|
def __setup_use_case(input)
|
141
|
-
self.class.
|
137
|
+
self.class.__flow_set__!
|
142
138
|
|
143
139
|
@__input = input
|
144
140
|
|
145
141
|
self.attributes = input
|
146
142
|
end
|
147
143
|
|
148
|
-
def __call
|
144
|
+
def __call!
|
149
145
|
return __call_use_case_flow if __call_use_case_flow?
|
150
146
|
|
151
147
|
__call_use_case
|
@@ -156,44 +152,55 @@ module Micro
|
|
156
152
|
|
157
153
|
return result if result.is_a?(Result)
|
158
154
|
|
159
|
-
raise Error::UnexpectedResult.new(self.class)
|
155
|
+
raise Error::UnexpectedResult.new("#{self.class.name}#call!")
|
160
156
|
end
|
161
157
|
|
162
158
|
def __call_use_case_flow?
|
163
|
-
self.class.
|
159
|
+
self.class.__flow_get__
|
164
160
|
end
|
165
161
|
|
166
162
|
def __call_use_case_flow
|
167
|
-
self.class.
|
163
|
+
self.class.__flow_get__.call(@__input)
|
168
164
|
end
|
169
165
|
|
170
|
-
def Success(
|
171
|
-
value
|
166
|
+
def Success(type = :ok, result: nil)
|
167
|
+
value = result || type
|
172
168
|
|
173
|
-
|
169
|
+
__get_result(true, value, type)
|
174
170
|
end
|
175
171
|
|
176
|
-
|
177
|
-
|
178
|
-
|
172
|
+
MapFailureType = -> (value, type) do
|
173
|
+
return type if type != :error
|
174
|
+
return value if value.is_a?(Symbol)
|
175
|
+
return :exception if value.is_a?(Exception)
|
179
176
|
|
180
|
-
|
177
|
+
type
|
181
178
|
end
|
182
179
|
|
183
|
-
def
|
184
|
-
|
185
|
-
return arg if arg.is_a?(Symbol)
|
186
|
-
return :exception if arg.is_a?(Exception)
|
180
|
+
def Failure(type = :error, result: nil)
|
181
|
+
value = result || type
|
187
182
|
|
188
|
-
type
|
183
|
+
type = MapFailureType.call(value, type)
|
184
|
+
|
185
|
+
__get_result(false, value, type)
|
189
186
|
end
|
190
187
|
|
191
|
-
def
|
188
|
+
def __result
|
192
189
|
@__result ||= Result.new
|
193
190
|
end
|
194
191
|
|
195
|
-
def
|
196
|
-
|
192
|
+
def __get_result(is_success, value, type)
|
193
|
+
__result.__set__(is_success, value, type, self)
|
197
194
|
end
|
195
|
+
|
196
|
+
private_constant :MapFailureType
|
197
|
+
end
|
198
|
+
|
199
|
+
def self.case?(arg)
|
200
|
+
(arg.is_a?(Class) && arg < Case) || arg.is_a?(Case)
|
201
|
+
end
|
202
|
+
|
203
|
+
def self.case_or_flow?(arg)
|
204
|
+
case?(arg) || arg.is_a?(Cases::Flow)
|
198
205
|
end
|
199
206
|
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,25 @@ 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
|
25
|
-
|
26
|
-
private_constant :Data
|
12
|
+
attr_reader :type, :data, :use_case
|
27
13
|
|
28
|
-
|
14
|
+
alias value data
|
29
15
|
|
30
16
|
def initialize
|
31
|
-
@__transitions__ =
|
32
|
-
@__transitions_accessible_attributes__ =
|
17
|
+
@__transitions__ = []
|
18
|
+
@__transitions_accessible_attributes__ = {}
|
33
19
|
end
|
34
20
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
@success, @value, @type, @use_case = is_success, value, type, use_case
|
21
|
+
def to_ary
|
22
|
+
[data, type]
|
23
|
+
end
|
40
24
|
|
41
|
-
|
25
|
+
def [](key)
|
26
|
+
data[key]
|
27
|
+
end
|
42
28
|
|
43
|
-
|
29
|
+
def values_at(*keys)
|
30
|
+
data.values_at(*keys)
|
44
31
|
end
|
45
32
|
|
46
33
|
def success?
|
@@ -51,106 +38,145 @@ module Micro
|
|
51
38
|
!success?
|
52
39
|
end
|
53
40
|
|
54
|
-
def
|
55
|
-
return
|
41
|
+
def on_success(expected_type = nil)
|
42
|
+
return self unless __success_type?(expected_type)
|
56
43
|
|
57
|
-
|
58
|
-
end
|
44
|
+
hook_data = expected_type.nil? ? self : data
|
59
45
|
|
60
|
-
|
61
|
-
yield(value) if success_type?(expected_type)
|
46
|
+
yield(hook_data, @use_case)
|
62
47
|
|
63
48
|
self
|
64
49
|
end
|
65
50
|
|
66
51
|
def on_failure(expected_type = nil)
|
67
|
-
return self unless
|
52
|
+
return self unless __failure_type?(expected_type)
|
68
53
|
|
69
|
-
|
54
|
+
hook_data = expected_type.nil? ? self : data
|
70
55
|
|
71
|
-
yield(
|
56
|
+
yield(hook_data, @use_case)
|
72
57
|
|
73
58
|
self
|
74
59
|
end
|
75
60
|
|
76
61
|
def on_exception(expected_exception = nil)
|
77
|
-
return self unless
|
62
|
+
return self unless __failure_type?(:exception)
|
78
63
|
|
79
|
-
if !expected_exception || (Kind.is(Exception, expected_exception) &&
|
80
|
-
yield(
|
64
|
+
if !expected_exception || (Kind.is(Exception, expected_exception) && data.fetch(:exception).is_a?(expected_exception))
|
65
|
+
yield(data, @use_case)
|
81
66
|
end
|
82
67
|
|
83
68
|
self
|
84
69
|
end
|
85
70
|
|
86
|
-
def then(
|
71
|
+
def then(use_case = nil, attributes = nil, &block)
|
87
72
|
can_yield_self = respond_to?(:yield_self)
|
88
73
|
|
89
74
|
if block
|
90
|
-
raise Error::InvalidInvocationOfTheThenMethod if
|
75
|
+
raise Error::InvalidInvocationOfTheThenMethod if use_case
|
91
76
|
raise NotImplementedError if !can_yield_self
|
92
77
|
|
93
78
|
yield_self(&block)
|
94
79
|
else
|
95
|
-
return yield_self if !
|
80
|
+
return yield_self if !use_case && can_yield_self
|
96
81
|
|
97
|
-
|
82
|
+
if use_case.is_a?(Proc)
|
83
|
+
return failure? ? self : __call_proc(use_case, expected: 'then(-> {})'.freeze)
|
84
|
+
end
|
85
|
+
|
86
|
+
# TODO: Test the then method with a Micro::Cases.{flow,safe_flow}() instance.
|
87
|
+
raise Error::InvalidInvocationOfTheThenMethod unless ::Micro.case_or_flow?(use_case)
|
98
88
|
|
99
89
|
return self if failure?
|
100
90
|
|
101
|
-
|
91
|
+
input = attributes.is_a?(Hash) ? self.data.merge(attributes) : self.data
|
92
|
+
|
93
|
+
use_case.__call_and_set_transition__(self, input)
|
102
94
|
end
|
103
95
|
end
|
104
96
|
|
97
|
+
def |(arg)
|
98
|
+
return self if failure?
|
99
|
+
|
100
|
+
return __call_proc(arg, expected: '| -> {}'.freeze) if arg.is_a?(Proc)
|
101
|
+
|
102
|
+
raise Error::InvalidInvocationOfTheThenMethod unless ::Micro.case_or_flow?(arg)
|
103
|
+
|
104
|
+
failure? ? self : arg.__call_and_set_transition__(self, data)
|
105
|
+
end
|
106
|
+
|
105
107
|
def transitions
|
106
|
-
|
108
|
+
@__transitions__.clone
|
109
|
+
end
|
107
110
|
|
108
|
-
|
111
|
+
FetchData = -> (data) do
|
112
|
+
return data if data.is_a?(Hash)
|
113
|
+
return { data => true } if data.is_a?(Symbol)
|
114
|
+
|
115
|
+
{ exception: data } if data.is_a?(Exception)
|
109
116
|
end
|
110
117
|
|
111
|
-
def
|
112
|
-
|
118
|
+
def __set__(is_success, data, type, use_case)
|
119
|
+
raise Error::InvalidResultType unless type.is_a?(Symbol)
|
120
|
+
raise Error::InvalidUseCase unless ::Micro.case?(use_case)
|
121
|
+
|
122
|
+
@success, @type, @use_case = is_success, type, use_case
|
123
|
+
|
124
|
+
@data = FetchData.call(data)
|
125
|
+
|
126
|
+
raise Micro::Case::Error::InvalidResult.new(is_success, type, use_case) unless @data
|
113
127
|
|
114
|
-
|
115
|
-
|
116
|
-
|
128
|
+
__set_transition unless @@transition_tracking_disabled
|
129
|
+
|
130
|
+
self
|
131
|
+
end
|
132
|
+
|
133
|
+
def __set_transitions_accessible_attributes__(attributes_data)
|
134
|
+
return attributes_data if @@transition_tracking_disabled
|
135
|
+
|
136
|
+
attributes = Utils.symbolize_hash_keys(attributes_data)
|
137
|
+
|
138
|
+
__update_transitions_accessible_attributes(attributes)
|
117
139
|
end
|
118
140
|
|
119
141
|
private
|
120
142
|
|
121
|
-
def
|
122
|
-
|
123
|
-
|
124
|
-
)
|
143
|
+
def __call_proc(arg, expected:)
|
144
|
+
result = arg.arity.zero? ? arg.call : arg.call(data.clone)
|
145
|
+
|
146
|
+
return result if result.is_a?(Result)
|
147
|
+
|
148
|
+
raise Error::UnexpectedResult.new("#{Result.name}##{expected}")
|
125
149
|
end
|
126
150
|
|
127
|
-
def
|
151
|
+
def __success_type?(expected_type)
|
128
152
|
success? && (expected_type.nil? || expected_type == type)
|
129
153
|
end
|
130
154
|
|
131
|
-
def
|
155
|
+
def __failure_type?(expected_type)
|
132
156
|
failure? && (expected_type.nil? || expected_type == type)
|
133
157
|
end
|
134
158
|
|
135
|
-
def
|
136
|
-
|
159
|
+
def __update_transitions_accessible_attributes(attributes)
|
160
|
+
@__transitions_accessible_attributes__.merge!(attributes)
|
161
|
+
@__transitions_accessible_attributes__
|
137
162
|
end
|
138
163
|
|
139
|
-
def
|
164
|
+
def __set_transition
|
140
165
|
use_case_class = @use_case.class
|
141
166
|
use_case_attributes = Utils.symbolize_hash_keys(@use_case.attributes)
|
142
167
|
|
143
|
-
|
168
|
+
__update_transitions_accessible_attributes(use_case_attributes)
|
144
169
|
|
145
170
|
result = @success ? :success : :failure
|
146
|
-
|
171
|
+
|
172
|
+
@__transitions__ << {
|
147
173
|
use_case: { class: use_case_class, attributes: use_case_attributes },
|
148
|
-
result => { type: @type,
|
149
|
-
accessible_attributes: @__transitions_accessible_attributes__.
|
174
|
+
result => { type: @type, result: data },
|
175
|
+
accessible_attributes: @__transitions_accessible_attributes__.keys
|
150
176
|
}
|
151
|
-
|
152
|
-
@__transitions__[use_case_class] = transition
|
153
177
|
end
|
178
|
+
|
179
|
+
private_constant :FetchData
|
154
180
|
end
|
155
181
|
end
|
156
182
|
end
|