u-case 2.4.0 → 3.0.0.rc3
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 +8 -0
- data/README.md +427 -314
- data/lib/micro/case.rb +67 -64
- data/lib/micro/case/config.rb +23 -0
- data/lib/micro/case/error.rb +20 -16
- data/lib/micro/case/result.rb +80 -55
- data/lib/micro/case/safe.rb +6 -2
- data/lib/micro/case/utils.rb +2 -2
- data/lib/micro/case/version.rb +1 -1
- data/lib/micro/case/with_activemodel_validation.rb +3 -1
- 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 +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,38 +3,51 @@
|
|
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
|
|
21
|
+
def self.config
|
22
|
+
yield(Config.instance)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.call(options = {})
|
26
|
+
new(options).call
|
27
|
+
end
|
28
|
+
|
20
29
|
def self.to_proc
|
21
30
|
Proc.new { |arg| call(arg) }
|
22
31
|
end
|
23
32
|
|
24
|
-
def self.
|
25
|
-
|
33
|
+
def self.call!
|
34
|
+
self
|
26
35
|
end
|
27
36
|
|
28
|
-
def self
|
29
|
-
|
37
|
+
def self.flow(*args)
|
38
|
+
@__flow_use_cases = args
|
30
39
|
end
|
31
40
|
|
32
|
-
def self
|
33
|
-
|
34
|
-
|
41
|
+
def self.inherited(subclass)
|
42
|
+
subclass.attributes(self.attributes_data({}))
|
43
|
+
subclass.extend ::Micro::Attributes.const_get('Macros::ForSubclasses'.freeze)
|
35
44
|
|
36
|
-
|
37
|
-
|
45
|
+
if self.send(:__flow_use_cases) && !subclass.name.to_s.end_with?(FLOW_STEP)
|
46
|
+
raise "Wooo, you can't do this! Inherits from a use case which has an inner flow violates "\
|
47
|
+
"one of the project principles: Solve complex business logic, by allowing the composition of use cases. "\
|
48
|
+
"Instead of doing this, declare a new class/constant with the steps needed.\n\n"\
|
49
|
+
"Related issue: https://github.com/serradura/u-case/issues/19\n"
|
50
|
+
end
|
38
51
|
end
|
39
52
|
|
40
53
|
def self.__new__(result, arg)
|
@@ -44,33 +57,38 @@ module Micro
|
|
44
57
|
end
|
45
58
|
|
46
59
|
def self.__call_and_set_transition__(result, arg)
|
47
|
-
|
48
|
-
result.__set_transitions_accessible_attributes__(arg
|
49
|
-
end
|
60
|
+
input =
|
61
|
+
arg.is_a?(Hash) ? result.__set_transitions_accessible_attributes__(arg) : arg
|
50
62
|
|
51
|
-
__new__(result,
|
63
|
+
__new__(result, input).call
|
52
64
|
end
|
53
65
|
|
54
|
-
def self.
|
55
|
-
|
56
|
-
|
57
|
-
const_set(:Flow_Step, Class.new(self) do
|
58
|
-
private def __call
|
59
|
-
__call_use_case
|
60
|
-
end
|
61
|
-
end)
|
66
|
+
def self.__flow_builder
|
67
|
+
Cases::Flow
|
62
68
|
end
|
63
69
|
|
64
|
-
def self.
|
65
|
-
|
70
|
+
def self.__flow_get
|
71
|
+
return @__flow if defined?(@__flow)
|
66
72
|
end
|
67
73
|
|
68
|
-
def self.
|
69
|
-
|
74
|
+
private_class_method def self.__flow_set(args)
|
75
|
+
return if __flow_get
|
76
|
+
|
77
|
+
def self.use_cases; __flow_get.use_cases; end
|
78
|
+
|
79
|
+
self.class_eval('def use_cases; self.class.use_cases; end')
|
80
|
+
|
81
|
+
@__flow = __flow_builder.build(args)
|
70
82
|
end
|
71
83
|
|
72
|
-
|
73
|
-
|
84
|
+
FLOW_STEP = 'Flow_Step'.freeze
|
85
|
+
|
86
|
+
private_constant :FLOW_STEP
|
87
|
+
|
88
|
+
def self.__call!
|
89
|
+
return const_get(FLOW_STEP) if const_defined?(FLOW_STEP, false)
|
90
|
+
|
91
|
+
class_eval("class #{FLOW_STEP} < #{self.name}; private def __call; __call_use_case; end; end")
|
74
92
|
end
|
75
93
|
|
76
94
|
private_class_method def self.__flow_use_cases
|
@@ -82,28 +100,10 @@ module Micro
|
|
82
100
|
.map { |use_case| use_case == self ? self.__call! : use_case }
|
83
101
|
end
|
84
102
|
|
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
103
|
def self.__flow_set!
|
100
104
|
__flow_set(__flow_use_cases_get) if !__flow_get && __flow_use_cases
|
101
105
|
end
|
102
106
|
|
103
|
-
def self.flow(*args)
|
104
|
-
__flow_use_cases_set(args)
|
105
|
-
end
|
106
|
-
|
107
107
|
def initialize(input)
|
108
108
|
__setup_use_case(input)
|
109
109
|
end
|
@@ -155,33 +155,36 @@ module Micro
|
|
155
155
|
self.class.__flow_get.call(@__input)
|
156
156
|
end
|
157
157
|
|
158
|
-
def Success(
|
159
|
-
value
|
158
|
+
def Success(type = :ok, result: nil)
|
159
|
+
value = result || type
|
160
160
|
|
161
|
-
|
161
|
+
__get_result(true, value, type)
|
162
162
|
end
|
163
163
|
|
164
|
-
|
165
|
-
|
166
|
-
|
164
|
+
MapFailureType = -> (value, type) do
|
165
|
+
return type if type != :error
|
166
|
+
return value if value.is_a?(Symbol)
|
167
|
+
return :exception if value.is_a?(Exception)
|
167
168
|
|
168
|
-
|
169
|
+
type
|
169
170
|
end
|
170
171
|
|
171
|
-
def
|
172
|
-
|
173
|
-
return arg if arg.is_a?(Symbol)
|
174
|
-
return :exception if arg.is_a?(Exception)
|
172
|
+
def Failure(type = :error, result: nil)
|
173
|
+
value = result || type
|
175
174
|
|
176
|
-
type
|
175
|
+
type = MapFailureType.call(value, type)
|
176
|
+
|
177
|
+
__get_result(false, value, type)
|
177
178
|
end
|
178
179
|
|
179
|
-
def
|
180
|
+
def __result__
|
180
181
|
@__result ||= Result.new
|
181
182
|
end
|
182
183
|
|
183
|
-
def
|
184
|
-
|
184
|
+
def __get_result(is_success, value, type)
|
185
|
+
__result__.__set__(is_success, value, type, self)
|
185
186
|
end
|
187
|
+
|
188
|
+
private_constant :MapFailureType
|
186
189
|
end
|
187
190
|
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
@@ -17,6 +17,24 @@ module Micro
|
|
17
17
|
def initialize; super('type must be a Symbol'.freeze); end
|
18
18
|
end
|
19
19
|
|
20
|
+
class InvalidResult < TypeError
|
21
|
+
def initialize(is_success, type, use_case)
|
22
|
+
base =
|
23
|
+
"The result returned from #{use_case.class.name}#call! must be a Hash."
|
24
|
+
|
25
|
+
result = is_success ? 'Success'.freeze : 'Failure'.freeze
|
26
|
+
|
27
|
+
example =
|
28
|
+
if type === :ok || type === :error || type === :exception
|
29
|
+
"#{result}(result: { key: 'value' })"
|
30
|
+
else
|
31
|
+
"#{result}(:#{type}, result: { key: 'value' })"
|
32
|
+
end
|
33
|
+
|
34
|
+
super("#{base}\n\nExample:\n #{example}")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
20
38
|
class InvalidResultInstance < ArgumentError
|
21
39
|
def initialize; super('argument must be an instance of Micro::Case::Result'.freeze); end
|
22
40
|
end
|
@@ -25,26 +43,12 @@ module Micro
|
|
25
43
|
def initialize; super('use case must be a kind or an instance of Micro::Case'.freeze); end
|
26
44
|
end
|
27
45
|
|
28
|
-
class InvalidUseCases < ArgumentError
|
29
|
-
def initialize; super('argument must be a collection of `Micro::Case` classes'.freeze); end
|
30
|
-
end
|
31
|
-
|
32
46
|
class InvalidInvocationOfTheThenMethod < StandardError
|
33
47
|
def initialize; super('Invalid invocation of the Micro::Case::Result#then method'); end
|
34
48
|
end
|
35
49
|
|
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
|
50
|
+
def self.by_wrong_usage?(exception)
|
51
|
+
exception.is_a?(InvalidResult) || exception.is_a?(UnexpectedResult) || exception.is_a?(ArgumentError)
|
48
52
|
end
|
49
53
|
end
|
50
54
|
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_method :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,25 +38,37 @@ 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
|
-
|
46
|
+
yield(hook_data, @use_case)
|
47
|
+
|
48
|
+
self
|
62
49
|
end
|
63
50
|
|
64
51
|
def on_failure(expected_type = nil)
|
65
52
|
return self unless failure_type?(expected_type)
|
66
53
|
|
67
|
-
|
54
|
+
hook_data = expected_type.nil? ? self : data
|
55
|
+
|
56
|
+
yield(hook_data, @use_case)
|
68
57
|
|
69
|
-
self
|
58
|
+
self
|
70
59
|
end
|
71
60
|
|
72
|
-
def
|
61
|
+
def on_exception(expected_exception = nil)
|
62
|
+
return self unless failure_type?(:exception)
|
63
|
+
|
64
|
+
if !expected_exception || (Kind.is(Exception, expected_exception) && data.fetch(:exception).is_a?(expected_exception))
|
65
|
+
yield(data, @use_case)
|
66
|
+
end
|
67
|
+
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
def then(arg = nil, attributes = nil, &block)
|
73
72
|
can_yield_self = respond_to?(:yield_self)
|
74
73
|
|
75
74
|
if block
|
@@ -84,31 +83,45 @@ module Micro
|
|
84
83
|
|
85
84
|
return self if failure?
|
86
85
|
|
87
|
-
|
86
|
+
input = attributes.is_a?(Hash) ? self.data.merge(attributes) : self.data
|
87
|
+
|
88
|
+
arg.__call_and_set_transition__(self, input)
|
88
89
|
end
|
89
90
|
end
|
90
91
|
|
91
92
|
def transitions
|
92
|
-
|
93
|
+
@__transitions__.clone
|
94
|
+
end
|
95
|
+
|
96
|
+
FetchData = -> (data) do
|
97
|
+
return data if data.is_a?(Hash)
|
98
|
+
return { data => true } if data.is_a?(Symbol)
|
93
99
|
|
94
|
-
|
100
|
+
{ exception: data } if data.is_a?(Exception)
|
95
101
|
end
|
96
102
|
|
97
|
-
def
|
98
|
-
|
103
|
+
def __set__(is_success, data, type, use_case)
|
104
|
+
raise Error::InvalidResultType unless type.is_a?(Symbol)
|
105
|
+
raise Error::InvalidUseCase if !is_a_use_case?(use_case)
|
106
|
+
|
107
|
+
@success, @type, @use_case = is_success, type, use_case
|
99
108
|
|
100
|
-
|
101
|
-
|
102
|
-
)
|
109
|
+
@data = FetchData.call(data)
|
110
|
+
|
111
|
+
raise Micro::Case::Error::InvalidResult.new(is_success, type, use_case) unless @data
|
112
|
+
|
113
|
+
__set_transition__ unless @@transition_tracking_disabled
|
114
|
+
|
115
|
+
self
|
103
116
|
end
|
104
117
|
|
105
|
-
|
118
|
+
def __set_transitions_accessible_attributes__(attributes_data)
|
119
|
+
return attributes_data if @@transition_tracking_disabled
|
106
120
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
end
|
121
|
+
__set_transitions_accessible_attributes__!(attributes_data)
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
112
125
|
|
113
126
|
def success_type?(expected_type)
|
114
127
|
success? && (expected_type.nil? || expected_type == type)
|
@@ -122,21 +135,33 @@ module Micro
|
|
122
135
|
(arg.is_a?(Class) && arg < ::Micro::Case) || arg.is_a?(::Micro::Case)
|
123
136
|
end
|
124
137
|
|
138
|
+
def __set_transitions_accessible_attributes__!(attributes_data)
|
139
|
+
attributes = Utils.symbolize_hash_keys(attributes_data)
|
140
|
+
|
141
|
+
__update_transitions_accessible_attributes__(attributes)
|
142
|
+
end
|
143
|
+
|
144
|
+
def __update_transitions_accessible_attributes__(attributes)
|
145
|
+
@__transitions_accessible_attributes__.merge!(attributes)
|
146
|
+
@__transitions_accessible_attributes__
|
147
|
+
end
|
148
|
+
|
125
149
|
def __set_transition__
|
126
150
|
use_case_class = @use_case.class
|
127
|
-
use_case_attributes = Utils.
|
151
|
+
use_case_attributes = Utils.symbolize_hash_keys(@use_case.attributes)
|
128
152
|
|
129
|
-
|
153
|
+
__update_transitions_accessible_attributes__(use_case_attributes)
|
130
154
|
|
131
155
|
result = @success ? :success : :failure
|
132
|
-
|
156
|
+
|
157
|
+
@__transitions__ << {
|
133
158
|
use_case: { class: use_case_class, attributes: use_case_attributes },
|
134
|
-
result => { type: @type,
|
135
|
-
accessible_attributes: @__transitions_accessible_attributes__.
|
159
|
+
result => { type: @type, result: data },
|
160
|
+
accessible_attributes: @__transitions_accessible_attributes__.keys
|
136
161
|
}
|
137
|
-
|
138
|
-
@__transitions__[use_case_class] = transition
|
139
162
|
end
|
163
|
+
|
164
|
+
private_constant :FetchData
|
140
165
|
end
|
141
166
|
end
|
142
167
|
end
|