u-case 2.1.1 → 2.5.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/.tool-versions +1 -1
- data/.travis.sh +5 -0
- data/.travis.yml +1 -0
- data/Gemfile +14 -1
- data/README.md +369 -83
- data/lib/micro/case.rb +93 -26
- data/lib/micro/case/error.rb +25 -9
- data/lib/micro/case/flow.rb +1 -1
- data/lib/micro/case/flow/reducer.rb +45 -16
- data/lib/micro/case/result.rb +89 -3
- data/lib/micro/case/safe.rb +1 -1
- data/lib/micro/case/safe/flow.rb +3 -3
- 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} +3 -3
- 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 +22 -6
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,35 +43,81 @@ module Micro
|
|
42
43
|
instance
|
43
44
|
end
|
44
45
|
|
45
|
-
def self.
|
46
|
-
|
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
|
47
52
|
end
|
48
53
|
|
49
|
-
|
50
|
-
def self.use_cases; __get_flow__.use_cases; end
|
54
|
+
FLOW_STEP = 'Flow_Step'.freeze
|
51
55
|
|
52
|
-
|
56
|
+
private_constant :FLOW_STEP
|
53
57
|
|
54
|
-
|
55
|
-
|
58
|
+
def self.__call!
|
59
|
+
return const_get(FLOW_STEP) if const_defined?(FLOW_STEP, false)
|
56
60
|
|
57
|
-
|
58
|
-
@__flow ||= __set_flow__(Flow::Reducer, args)
|
61
|
+
class_eval("class #{FLOW_STEP} < #{self.name}; private def __call; __call_use_case; end; end")
|
59
62
|
end
|
60
63
|
|
61
64
|
def self.call!
|
62
|
-
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.inherited(subclass)
|
69
|
+
subclass.attributes(self.attributes_data({}))
|
70
|
+
subclass.extend ::Micro::Attributes.const_get('Macros::ForSubclasses'.freeze)
|
71
|
+
|
72
|
+
if self.send(:__flow_use_cases) && !subclass.name.to_s.end_with?(FLOW_STEP)
|
73
|
+
raise "Wooo, you can't do this! Inherits from a use case which has an inner flow violates "\
|
74
|
+
"one of the project principles: Solve complex business logic, by allowing the composition of use cases. "\
|
75
|
+
"Instead of doing this, declare a new class/constant with the steps needed.\n\n"\
|
76
|
+
"Related issue: https://github.com/serradura/u-case/issues/19\n"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.__flow_reducer
|
81
|
+
Flow::Reducer
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.__flow_get
|
85
|
+
return @__flow if defined?(@__flow)
|
86
|
+
end
|
87
|
+
|
88
|
+
private_class_method def self.__flow_use_cases
|
89
|
+
return @__flow_use_cases if defined?(@__flow_use_cases)
|
90
|
+
end
|
91
|
+
|
92
|
+
private_class_method def self.__flow_use_cases_get
|
93
|
+
Array(__flow_use_cases)
|
94
|
+
.map { |use_case| use_case == self ? self.__call! : use_case }
|
95
|
+
end
|
96
|
+
|
97
|
+
private_class_method def self.__flow_use_cases_set(args)
|
98
|
+
@__flow_use_cases = args
|
99
|
+
end
|
100
|
+
|
101
|
+
private_class_method def self.__flow_set(args)
|
102
|
+
return if __flow_get
|
103
|
+
|
104
|
+
def self.use_cases; __flow_get.use_cases; end
|
105
|
+
|
106
|
+
self.class_eval('def use_cases; self.class.use_cases; end')
|
107
|
+
|
108
|
+
@__flow = __flow_reducer.build(args)
|
109
|
+
end
|
63
110
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
111
|
+
def self.__flow_set!
|
112
|
+
__flow_set(__flow_use_cases_get) if !__flow_get && __flow_use_cases
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.flow(*args)
|
116
|
+
__flow_use_cases_set(args)
|
69
117
|
end
|
70
118
|
|
71
119
|
def initialize(input)
|
72
|
-
|
73
|
-
self.attributes = input
|
120
|
+
__setup_use_case(input)
|
74
121
|
end
|
75
122
|
|
76
123
|
def call!
|
@@ -83,15 +130,23 @@ module Micro
|
|
83
130
|
|
84
131
|
def __set_result__(result)
|
85
132
|
raise Error::InvalidResultInstance unless result.is_a?(Result)
|
86
|
-
raise Error::ResultIsAlreadyDefined if @__result
|
133
|
+
raise Error::ResultIsAlreadyDefined if defined?(@__result)
|
87
134
|
|
88
135
|
@__result = result
|
89
136
|
end
|
90
137
|
|
91
138
|
private
|
92
139
|
|
140
|
+
def __setup_use_case(input)
|
141
|
+
self.class.__flow_set!
|
142
|
+
|
143
|
+
@__input = input
|
144
|
+
|
145
|
+
self.attributes = input
|
146
|
+
end
|
147
|
+
|
93
148
|
def __call
|
94
|
-
return
|
149
|
+
return __call_use_case_flow if __call_use_case_flow?
|
95
150
|
|
96
151
|
__call_use_case
|
97
152
|
end
|
@@ -104,21 +159,25 @@ module Micro
|
|
104
159
|
raise Error::UnexpectedResult.new(self.class)
|
105
160
|
end
|
106
161
|
|
162
|
+
def __call_use_case_flow?
|
163
|
+
self.class.__flow_get
|
164
|
+
end
|
165
|
+
|
166
|
+
def __call_use_case_flow
|
167
|
+
self.class.__flow_get.call(@__input)
|
168
|
+
end
|
169
|
+
|
107
170
|
def Success(arg = :ok)
|
108
171
|
value, type = block_given? ? [yield, arg] : [arg, :ok]
|
109
172
|
|
110
|
-
|
173
|
+
__get_result_with(true, value, type)
|
111
174
|
end
|
112
175
|
|
113
176
|
def Failure(arg = :error)
|
114
177
|
value = block_given? ? yield : arg
|
115
178
|
type = __map_failure_type(value, block_given? ? arg : :error)
|
116
179
|
|
117
|
-
|
118
|
-
end
|
119
|
-
|
120
|
-
def __get_result__
|
121
|
-
@__result ||= Result.new
|
180
|
+
__get_result_with(false, value, type)
|
122
181
|
end
|
123
182
|
|
124
183
|
def __map_failure_type(arg, type)
|
@@ -128,5 +187,13 @@ module Micro
|
|
128
187
|
|
129
188
|
type
|
130
189
|
end
|
190
|
+
|
191
|
+
def __get_result__
|
192
|
+
@__result ||= Result.new
|
193
|
+
end
|
194
|
+
|
195
|
+
def __get_result_with(is_success, value, type)
|
196
|
+
__get_result__.__set__(is_success, value, type, self)
|
197
|
+
end
|
131
198
|
end
|
132
199
|
end
|
data/lib/micro/case/error.rb
CHANGED
@@ -9,20 +9,36 @@ module Micro
|
|
9
9
|
def initialize(klass); super(klass.name + MESSAGE); end
|
10
10
|
end
|
11
11
|
|
12
|
-
ResultIsAlreadyDefined
|
12
|
+
class ResultIsAlreadyDefined < ArgumentError
|
13
|
+
def initialize; super('result is already defined'.freeze); end
|
14
|
+
end
|
13
15
|
|
14
|
-
InvalidResultType
|
15
|
-
|
16
|
+
class InvalidResultType < TypeError
|
17
|
+
def initialize; super('type must be a Symbol'.freeze); end
|
18
|
+
end
|
16
19
|
|
17
|
-
|
18
|
-
|
20
|
+
class InvalidResultInstance < ArgumentError
|
21
|
+
def initialize; super('argument must be an instance of Micro::Case::Result'.freeze); end
|
22
|
+
end
|
19
23
|
|
20
|
-
|
24
|
+
class InvalidUseCase < TypeError
|
25
|
+
def initialize; super('use case must be a kind or an instance of Micro::Case'.freeze); end
|
26
|
+
end
|
21
27
|
|
22
|
-
class
|
23
|
-
|
28
|
+
class InvalidUseCases < ArgumentError
|
29
|
+
def initialize; super('argument must be a collection of `Micro::Case` classes'.freeze); end
|
30
|
+
end
|
24
31
|
|
25
|
-
|
32
|
+
class InvalidInvocationOfTheThenMethod < StandardError
|
33
|
+
def initialize; super('Invalid invocation of the Micro::Case::Result#then method'); end
|
34
|
+
end
|
35
|
+
|
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
|
+
class InvalidAccessToTheUseCaseObject < StandardError
|
41
|
+
def initialize; super('only a failure result can access its use case object'.freeze); end
|
26
42
|
end
|
27
43
|
|
28
44
|
module ByWrongUsage
|
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
|
|
@@ -41,7 +58,9 @@ module Micro
|
|
41
58
|
end
|
42
59
|
|
43
60
|
def on_success(expected_type = nil)
|
44
|
-
|
61
|
+
yield(value) if success_type?(expected_type)
|
62
|
+
|
63
|
+
self
|
45
64
|
end
|
46
65
|
|
47
66
|
def on_failure(expected_type = nil)
|
@@ -49,11 +68,62 @@ module Micro
|
|
49
68
|
|
50
69
|
data = expected_type.nil? ? Data.new(value, type).tap(&:freeze) : value
|
51
70
|
|
52
|
-
|
71
|
+
yield(data, @use_case)
|
72
|
+
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
def on_exception(expected_exception = nil)
|
77
|
+
return self unless failure_type?(:exception)
|
78
|
+
|
79
|
+
if !expected_exception || (Kind.is(Exception, expected_exception) && value.is_a?(expected_exception))
|
80
|
+
yield(value, @use_case)
|
81
|
+
end
|
82
|
+
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
def then(arg = nil, &block)
|
87
|
+
can_yield_self = respond_to?(:yield_self)
|
88
|
+
|
89
|
+
if block
|
90
|
+
raise Error::InvalidInvocationOfTheThenMethod if arg
|
91
|
+
raise NotImplementedError if !can_yield_self
|
92
|
+
|
93
|
+
yield_self(&block)
|
94
|
+
else
|
95
|
+
return yield_self if !arg && can_yield_self
|
96
|
+
|
97
|
+
raise Error::InvalidInvocationOfTheThenMethod if !is_a_use_case?(arg)
|
98
|
+
|
99
|
+
return self if failure?
|
100
|
+
|
101
|
+
arg.__call_and_set_transition__(self, self.value)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def transitions
|
106
|
+
return [] if @__transitions__.empty?
|
107
|
+
|
108
|
+
@__transitions__.map { |_use_case, transition| transition }
|
109
|
+
end
|
110
|
+
|
111
|
+
def __set_transitions_accessible_attributes__(attribute_names)
|
112
|
+
return if @@transition_tracking_disabled
|
113
|
+
|
114
|
+
__set_transitions_accessible_attributes__!(
|
115
|
+
attribute_names.map!(&:to_sym)
|
116
|
+
)
|
53
117
|
end
|
54
118
|
|
55
119
|
private
|
56
120
|
|
121
|
+
def __set_transitions_accessible_attributes__!(attribute_names)
|
122
|
+
@__transitions_accessible_attributes__.merge(
|
123
|
+
attribute_names
|
124
|
+
)
|
125
|
+
end
|
126
|
+
|
57
127
|
def success_type?(expected_type)
|
58
128
|
success? && (expected_type.nil? || expected_type == type)
|
59
129
|
end
|
@@ -65,6 +135,22 @@ module Micro
|
|
65
135
|
def is_a_use_case?(arg)
|
66
136
|
(arg.is_a?(Class) && arg < ::Micro::Case) || arg.is_a?(::Micro::Case)
|
67
137
|
end
|
138
|
+
|
139
|
+
def __set_transition__
|
140
|
+
use_case_class = @use_case.class
|
141
|
+
use_case_attributes = Utils.symbolize_hash_keys(@use_case.attributes)
|
142
|
+
|
143
|
+
__set_transitions_accessible_attributes__!(use_case_attributes.keys)
|
144
|
+
|
145
|
+
result = @success ? :success : :failure
|
146
|
+
transition = {
|
147
|
+
use_case: { class: use_case_class, attributes: use_case_attributes },
|
148
|
+
result => { type: @type, value: @value },
|
149
|
+
accessible_attributes: @__transitions_accessible_attributes__.to_a
|
150
|
+
}
|
151
|
+
|
152
|
+
@__transitions__[use_case_class] = transition
|
153
|
+
end
|
68
154
|
end
|
69
155
|
end
|
70
156
|
end
|