u-case 3.0.0 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,6 +7,7 @@ require 'micro/case/version'
7
7
 
8
8
  module Micro
9
9
  class Case
10
+ require 'micro/cases/utils'
10
11
  require 'micro/case/utils'
11
12
  require 'micro/case/error'
12
13
  require 'micro/case/result'
@@ -16,26 +17,32 @@ module Micro
16
17
 
17
18
  require 'micro/cases'
18
19
 
19
- include Micro::Attributes.without(:strict_initialize)
20
+ include Micro::Attributes
20
21
 
21
- def self.call(options = Kind::Empty::HASH)
22
- new(options).__call__
22
+ def self.call(input = Kind::Empty::HASH)
23
+ result = __new__(Result.new, input).__call__
24
+
25
+ return result unless block_given?
26
+
27
+ yield Result::Wrapper.new(result)
23
28
  end
24
29
 
25
- INVALID_INVOCATION_OF_THE_THE_METHOD =
26
- Error::InvalidInvocationOfTheThenMethod.new(self.name)
30
+ INVALID_INVOCATION_OF_THE_THEN_METHOD =
31
+ Error::InvalidInvocationOfTheThenMethod.new("#{self.name}.")
27
32
 
28
33
  def self.then(use_case = nil, &block)
29
34
  can_yield_self = respond_to?(:yield_self)
30
35
 
31
36
  if block
32
- raise INVALID_INVOCATION_OF_THE_THE_METHOD if use_case
37
+ raise INVALID_INVOCATION_OF_THE_THEN_METHOD if use_case
33
38
  raise NotImplementedError if !can_yield_self
34
39
 
35
40
  yield_self(&block)
36
41
  else
37
42
  return yield_self if !use_case && can_yield_self
38
43
 
44
+ raise INVALID_INVOCATION_OF_THE_THEN_METHOD unless ::Micro.case_or_flow?(use_case)
45
+
39
46
  self.call.then(use_case)
40
47
  end
41
48
  end
@@ -45,7 +52,7 @@ module Micro
45
52
  end
46
53
 
47
54
  def self.flow(*args)
48
- @__flow_use_cases = args
55
+ @__flow_use_cases = Cases::Utils.map_use_cases(args)
49
56
  end
50
57
 
51
58
  class << self
@@ -61,7 +68,8 @@ module Micro
61
68
  end
62
69
 
63
70
  def self.inherited(subclass)
64
- subclass.attributes(self.attributes_data({}))
71
+ subclass.__attributes_set_after_inherit__(self.__attributes_data__)
72
+
65
73
  subclass.extend ::Micro::Attributes.const_get('Macros::ForSubclasses'.freeze)
66
74
 
67
75
  if self.send(:__flow_use_cases) && !subclass.name.to_s.end_with?(FLOW_STEP)
@@ -78,6 +86,8 @@ module Micro
78
86
  new(input).__set_result__(result)
79
87
  end
80
88
 
89
+ private_class_method :new
90
+
81
91
  def self.__flow_builder__
82
92
  Cases::Flow
83
93
  end
@@ -119,6 +129,28 @@ module Micro
119
129
  __flow_set(__flow_use_cases_get) if !__flow_get__ && __flow_use_cases
120
130
  end
121
131
 
132
+ InspectKey = :__inspect_key__ # :nodoc:
133
+
134
+ def self.inspect
135
+ ids = (Thread.current[InspectKey] ||= [])
136
+
137
+ if ids.include?(object_id)
138
+ return sprintf('#<%s: {...}>', self)
139
+ end
140
+
141
+ begin
142
+ ids << object_id
143
+
144
+ if __flow_use_cases
145
+ return '<%s (%s) use_cases=%s>' % [self, __flow_builder__, @__flow_use_cases]
146
+ else
147
+ return '<%s (%s) attributes=%s>' % [self, self.superclass, attributes]
148
+ end
149
+ ensure
150
+ ids.pop
151
+ end
152
+ end
153
+
122
154
  def initialize(input)
123
155
  __setup_use_case(input)
124
156
  end
@@ -128,7 +160,7 @@ module Micro
128
160
  end
129
161
 
130
162
  def __call__
131
- call
163
+ __call_the_use_case_or_its_flow
132
164
  end
133
165
 
134
166
  def __set_result__(result)
@@ -142,8 +174,21 @@ module Micro
142
174
 
143
175
  private
144
176
 
145
- def call
146
- return __call_use_case_flow if __call_use_case_flow?
177
+ def call(use_case, defaults = Kind::Empty::HASH)
178
+ raise Error::InvalidUseCase unless ::Micro.case_or_flow?(use_case)
179
+
180
+ input =
181
+ defaults.empty? ? attributes : attributes.merge(Utils::Hashes.stringify_keys(defaults))
182
+
183
+ use_case.__new__(@__result, input).__call__
184
+ end
185
+
186
+ def apply(name)
187
+ method(name)
188
+ end
189
+
190
+ def __call_the_use_case_or_its_flow
191
+ return __call_the_use_case_flow if __call_the_use_case_flow?
147
192
 
148
193
  __call_use_case
149
194
  end
@@ -164,11 +209,11 @@ module Micro
164
209
  raise Error::UnexpectedResult.new("#{self.class.name}#call!")
165
210
  end
166
211
 
167
- def __call_use_case_flow?
212
+ def __call_the_use_case_flow?
168
213
  self.class.__flow_get__
169
214
  end
170
215
 
171
- def __call_use_case_flow
216
+ def __call_the_use_case_flow
172
217
  self.class.__flow_get__.call(@__input)
173
218
  end
174
219
 
@@ -194,18 +239,32 @@ module Micro
194
239
  __get_result(false, value, type)
195
240
  end
196
241
 
197
- def __result
198
- @__result ||= Result.new
242
+ def __get_result(is_success, value, type)
243
+ @__result.__set__(is_success, value, type, self)
199
244
  end
200
245
 
201
- def __get_result(is_success, value, type)
202
- __result.__set__(is_success, value, type, self)
246
+ def transaction(adapter = :activerecord)
247
+ raise NotImplementedError unless adapter == :activerecord
248
+
249
+ result = nil
250
+
251
+ ActiveRecord::Base.transaction do
252
+ result = yield
253
+
254
+ raise ActiveRecord::Rollback if result.failure?
255
+ end
256
+
257
+ result
203
258
  end
204
259
 
205
- private_constant :MapFailureType
260
+ private_constant :MapFailureType, :INVALID_INVOCATION_OF_THE_THEN_METHOD
261
+ end
262
+
263
+ def self.case?(arg)
264
+ arg.is_a?(Class) && arg < Case
206
265
  end
207
266
 
208
267
  def self.case_or_flow?(arg)
209
- (arg.is_a?(Class) && arg < Case) || arg.is_a?(Cases::Flow)
268
+ case?(arg) || arg.is_a?(Cases::Flow)
210
269
  end
211
270
  end
@@ -26,7 +26,7 @@ module Micro
26
26
  end
27
27
 
28
28
  def activemodel_validation_errors_failure
29
- @activemodel_validation_errors_failure if defined?(@activemodel_validation_errors_failure)
29
+ return @activemodel_validation_errors_failure if defined?(@activemodel_validation_errors_failure)
30
30
 
31
31
  @activemodel_validation_errors_failure = :invalid_attributes
32
32
  end
@@ -47,12 +47,15 @@ module Micro
47
47
 
48
48
  class InvalidInvocationOfTheThenMethod < StandardError
49
49
  def initialize(class_name)
50
- super("Invalid invocation of the #{class_name}#then method")
50
+ super("Invalid invocation of the #{class_name}then method")
51
51
  end
52
52
  end
53
53
 
54
54
  def self.by_wrong_usage?(exception)
55
- exception.is_a?(InvalidResult) || exception.is_a?(UnexpectedResult) || exception.is_a?(ArgumentError)
55
+ case exception
56
+ when Kind::Error, ArgumentError, InvalidResult, UnexpectedResult then true
57
+ else false
58
+ end
56
59
  end
57
60
  end
58
61
  end
@@ -5,10 +5,13 @@ require 'set'
5
5
  module Micro
6
6
  class Case
7
7
  class Result
8
+ require 'micro/case/result/wrapper'
9
+ require 'micro/case/result/transitions'
10
+
8
11
  Kind::Types.add(self)
9
12
 
10
- INVALID_INVOCATION_OF_THE_THE_METHOD =
11
- Error::InvalidInvocationOfTheThenMethod.new(self.name)
13
+ INVALID_INVOCATION_OF_THE_THEN_METHOD =
14
+ Error::InvalidInvocationOfTheThenMethod.new("#{self.name}#")
12
15
 
13
16
  @@transitions_enabled = true
14
17
 
@@ -20,16 +23,35 @@ module Micro
20
23
 
21
24
  alias value data
22
25
 
23
- def initialize
24
- @__transitions = @@transitions_enabled ? [] : Kind::Empty::ARRAY
26
+ def initialize(transitions_mapper = Transitions::MapEverything)
27
+ enable_transitions = @@transitions_enabled
28
+
29
+ @__is_unknown = true
25
30
  @__accumulated_data = {}
31
+ @__tracked_use_cases = Set.new
26
32
  @__accessible_attributes = {}
33
+
34
+ @__transitions = enable_transitions ? [] : Kind::Empty::ARRAY
35
+ @__transitions_mapper = transitions_mapper if enable_transitions
36
+ end
37
+
38
+ def inspect
39
+ pretty_type = @__success ? 'Success' : 'Failure'
40
+
41
+ instance_info = '%s (%s) type=:%s data=%s' % [pretty_type, self.class, @type, data]
42
+ transitions_info = ' transitions=%d' % [@__transitions.size] if Micro::Case::Result.transitions_enabled?
43
+
44
+ "<#{instance_info}#{transitions_info}>"
27
45
  end
28
46
 
29
47
  def to_ary
30
48
  [data, type]
31
49
  end
32
50
 
51
+ def to_sym
52
+ @__success ? :success : :failure
53
+ end
54
+
33
55
  def [](key)
34
56
  data[key]
35
57
  end
@@ -47,20 +69,29 @@ module Micro
47
69
  end
48
70
 
49
71
  def slice(*keys)
50
- Utils.slice_hash(data, keys)
72
+ Utils::Hashes.slice(data, keys)
51
73
  end
52
74
 
53
75
  def success?
54
- @success
76
+ @__success
55
77
  end
56
78
 
57
79
  def failure?
58
80
  !success?
59
81
  end
60
82
 
83
+ def unknown?
84
+ @__is_unknown
85
+ end
86
+
87
+ def accessible_attributes
88
+ @__accessible_attributes.keys
89
+ end
90
+
61
91
  def on_success(expected_type = nil)
62
92
  return self unless __success_type?(expected_type)
63
93
 
94
+ @__is_unknown = false
64
95
  hook_data = expected_type.nil? ? self : data
65
96
 
66
97
  yield(hook_data, @use_case)
@@ -71,6 +102,7 @@ module Micro
71
102
  def on_failure(expected_type = nil)
72
103
  return self unless __failure_type?(expected_type)
73
104
 
105
+ @__is_unknown = false
74
106
  hook_data = expected_type.nil? ? self : data
75
107
 
76
108
  yield(hook_data, @use_case)
@@ -88,11 +120,19 @@ module Micro
88
120
  self
89
121
  end
90
122
 
123
+ def on_unknown
124
+ return self unless unknown?
125
+
126
+ yield(self, @use_case)
127
+
128
+ self
129
+ end
130
+
91
131
  def then(use_case = nil, attributes = nil, &block)
92
132
  can_yield_self = respond_to?(:yield_self)
93
133
 
94
134
  if block
95
- raise INVALID_INVOCATION_OF_THE_THE_METHOD if use_case
135
+ raise INVALID_INVOCATION_OF_THE_THEN_METHOD if use_case
96
136
  raise NotImplementedError if !can_yield_self
97
137
 
98
138
  yield_self(&block)
@@ -101,7 +141,7 @@ module Micro
101
141
  return failure? ? self : __call_proc(use_case, 'then(-> {})'.freeze) if use_case.is_a?(Proc)
102
142
  return failure? ? self : __call_method(use_case, attributes) if use_case.is_a?(Method)
103
143
 
104
- raise INVALID_INVOCATION_OF_THE_THE_METHOD unless ::Micro.case_or_flow?(use_case)
144
+ raise INVALID_INVOCATION_OF_THE_THEN_METHOD unless ::Micro.case_or_flow?(use_case)
105
145
 
106
146
  return self if failure?
107
147
 
@@ -121,13 +161,13 @@ module Micro
121
161
  return __call_proc(arg, '| -> {}'.freeze) if arg.is_a?(Proc)
122
162
  return __call_method(arg) if arg.is_a?(Method)
123
163
 
124
- raise INVALID_INVOCATION_OF_THE_THE_METHOD unless ::Micro.case_or_flow?(arg)
164
+ raise INVALID_INVOCATION_OF_THE_THEN_METHOD unless ::Micro.case_or_flow?(arg)
125
165
 
126
166
  failure? ? self : arg.__new__(self, data).__call__
127
167
  end
128
168
 
129
169
  def transitions
130
- @__transitions.clone
170
+ @__transitions.dup
131
171
  end
132
172
 
133
173
  FetchData = -> (data) do
@@ -141,17 +181,21 @@ module Micro
141
181
  raise Error::InvalidResultType unless type.is_a?(Symbol)
142
182
  raise Error::InvalidUseCase unless use_case.is_a?(::Micro::Case)
143
183
 
144
- @success, @type, @use_case = is_success, type, use_case
184
+ @__success, @type, @use_case = is_success, type, use_case
145
185
 
146
- @data = FetchData.call(data)
186
+ @data = FetchData.call(data).freeze
147
187
 
148
188
  raise Micro::Case::Error::InvalidResult.new(is_success, type, use_case) unless @data
149
189
 
150
190
  @__accumulated_data.merge!(@data)
151
191
 
152
- use_case_attributes = Utils.symbolize_hash_keys(@use_case.attributes)
192
+ use_case_attributes = Utils::Hashes.symbolize_keys(@use_case.attributes)
153
193
 
154
- __update_accessible_attributes(use_case_attributes)
194
+ unless @__tracked_use_cases.member?(use_case_class = @use_case.class)
195
+ @__tracked_use_cases.add(use_case_class)
196
+
197
+ __update_accessible_attributes(use_case_attributes)
198
+ end
155
199
 
156
200
  __set_transition(use_case_attributes) unless @__transitions.frozen?
157
201
 
@@ -161,23 +205,26 @@ module Micro
161
205
  def __set_accessible_attributes__(arg)
162
206
  return arg unless arg.is_a?(Hash)
163
207
 
164
- attributes = Utils.symbolize_hash_keys(arg)
208
+ attributes = Utils::Hashes.symbolize_keys(arg)
165
209
 
166
210
  __update_accessible_attributes(attributes)
211
+ __fetch_accessible_attributes
167
212
  end
168
213
 
169
214
  private
170
215
 
171
- def __fetch_accumulated_data(opt = nil)
172
- __update_accessible_attributes(
173
- opt ? opt.merge(@__accumulated_data) : @__accumulated_data
174
- )
216
+ def __update_accessible_attributes(attributes)
217
+ @__accessible_attributes.merge!(attributes)
218
+ end
219
+
220
+ def __fetch_accessible_attributes
221
+ @__accessible_attributes.dup
175
222
  end
176
223
 
177
224
  def __call_proc(fn, expected)
178
- input = __fetch_accumulated_data
225
+ __update_accessible_attributes(@__accumulated_data)
179
226
 
180
- result = fn.arity.zero? ? fn.call : fn.call(input)
227
+ result = fn.arity.zero? ? fn.call : fn.call(__fetch_accessible_attributes)
181
228
 
182
229
  return self if result === self
183
230
 
@@ -185,9 +232,9 @@ module Micro
185
232
  end
186
233
 
187
234
  def __call_method(methd, attributes = nil)
188
- input = __fetch_accumulated_data(attributes)
235
+ __update_accessible_attributes(attributes ? attributes.merge(@__accumulated_data) : @__accumulated_data)
189
236
 
190
- result = methd.arity.zero? ? methd.call : methd.call(**input)
237
+ result = methd.arity.zero? ? methd.call : methd.call(**__fetch_accessible_attributes)
191
238
 
192
239
  return self if result === self
193
240
 
@@ -202,24 +249,11 @@ module Micro
202
249
  failure? && (expected_type.nil? || expected_type == type)
203
250
  end
204
251
 
205
- def __update_accessible_attributes(attributes)
206
- @__accessible_attributes.merge!(attributes)
207
- @__accessible_attributes.dup
208
- end
209
-
210
252
  def __set_transition(use_case_attributes)
211
- use_case_class = @use_case.class
212
-
213
- result = @success ? :success : :failure
214
-
215
- @__transitions << {
216
- use_case: { class: use_case_class, attributes: use_case_attributes },
217
- result => { type: @type, result: data },
218
- accessible_attributes: @__accessible_attributes.keys
219
- }
253
+ @__transitions << @__transitions_mapper.call(self, use_case_attributes)
220
254
  end
221
255
 
222
- private_constant :FetchData, :INVALID_INVOCATION_OF_THE_THE_METHOD
256
+ private_constant :FetchData, :INVALID_INVOCATION_OF_THE_THEN_METHOD
223
257
  end
224
258
  end
225
259
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ class Case
5
+ class Result
6
+ class Transitions
7
+ MapEverything = -> (result, use_case_attributes) do
8
+ {
9
+ use_case: { class: result.use_case.class, attributes: use_case_attributes },
10
+ result.to_sym => { type: result.type, result: result.data },
11
+ accessible_attributes: result.accessible_attributes
12
+ }
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end