u-case 4.0.0 → 4.2.2

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,10 +17,18 @@ module Micro
16
17
 
17
18
  require 'micro/cases'
18
19
 
19
- include Micro::Attributes.with(:initialize, :diff)
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
+ result_wrapper = Result::Wrapper.new(result)
28
+
29
+ yield(result_wrapper)
30
+
31
+ result_wrapper.output
23
32
  end
24
33
 
25
34
  INVALID_INVOCATION_OF_THE_THEN_METHOD =
@@ -47,7 +56,7 @@ module Micro
47
56
  end
48
57
 
49
58
  def self.flow(*args)
50
- @__flow_use_cases = args
59
+ @__flow_use_cases = Cases::Utils.map_use_cases(args)
51
60
  end
52
61
 
53
62
  class << self
@@ -81,6 +90,8 @@ module Micro
81
90
  new(input).__set_result__(result)
82
91
  end
83
92
 
93
+ private_class_method :new
94
+
84
95
  def self.__flow_builder__
85
96
  Cases::Flow
86
97
  end
@@ -122,6 +133,28 @@ module Micro
122
133
  __flow_set(__flow_use_cases_get) if !__flow_get__ && __flow_use_cases
123
134
  end
124
135
 
136
+ InspectKey = :__inspect_key__ # :nodoc:
137
+
138
+ def self.inspect
139
+ ids = (Thread.current[InspectKey] ||= [])
140
+
141
+ if ids.include?(object_id)
142
+ return sprintf('#<%s: ...>', self)
143
+ end
144
+
145
+ begin
146
+ ids << object_id
147
+
148
+ if __flow_use_cases
149
+ return '<%s (%s) use_cases=%s>' % [self, __flow_builder__, @__flow_use_cases]
150
+ else
151
+ return '<%s (%s) attributes=%s>' % [self, self.superclass, attributes]
152
+ end
153
+ ensure
154
+ ids.pop
155
+ end
156
+ end
157
+
125
158
  def initialize(input)
126
159
  __setup_use_case(input)
127
160
  end
@@ -131,7 +164,7 @@ module Micro
131
164
  end
132
165
 
133
166
  def __call__
134
- call
167
+ __call_the_use_case_or_its_flow
135
168
  end
136
169
 
137
170
  def __set_result__(result)
@@ -145,12 +178,21 @@ module Micro
145
178
 
146
179
  private
147
180
 
181
+ def call(use_case, defaults = Kind::Empty::HASH)
182
+ raise Error::InvalidUseCase unless ::Micro.case_or_flow?(use_case)
183
+
184
+ input =
185
+ defaults.empty? ? attributes : attributes.merge(Utils::Hashes.stringify_keys(defaults))
186
+
187
+ use_case.__new__(@__result, input).__call__
188
+ end
189
+
148
190
  def apply(name)
149
191
  method(name)
150
192
  end
151
193
 
152
- def call
153
- return __call_use_case_flow if __call_use_case_flow?
194
+ def __call_the_use_case_or_its_flow
195
+ return __call_the_use_case_flow if __call_the_use_case_flow?
154
196
 
155
197
  __call_use_case
156
198
  end
@@ -171,11 +213,11 @@ module Micro
171
213
  raise Error::UnexpectedResult.new("#{self.class.name}#call!")
172
214
  end
173
215
 
174
- def __call_use_case_flow?
216
+ def __call_the_use_case_flow?
175
217
  self.class.__flow_get__
176
218
  end
177
219
 
178
- def __call_use_case_flow
220
+ def __call_the_use_case_flow
179
221
  self.class.__flow_get__.call(@__input)
180
222
  end
181
223
 
@@ -201,18 +243,32 @@ module Micro
201
243
  __get_result(false, value, type)
202
244
  end
203
245
 
204
- def __result
205
- @__result ||= Result.new
246
+ def __get_result(is_success, value, type)
247
+ @__result.__set__(is_success, value, type, self)
206
248
  end
207
249
 
208
- def __get_result(is_success, value, type)
209
- __result.__set__(is_success, value, type, self)
250
+ def transaction(adapter = :activerecord)
251
+ raise NotImplementedError unless adapter == :activerecord
252
+
253
+ result = nil
254
+
255
+ ActiveRecord::Base.transaction do
256
+ result = yield
257
+
258
+ raise ActiveRecord::Rollback if result.failure?
259
+ end
260
+
261
+ result
210
262
  end
211
263
 
212
264
  private_constant :MapFailureType, :INVALID_INVOCATION_OF_THE_THEN_METHOD
213
265
  end
214
266
 
267
+ def self.case?(arg)
268
+ arg.is_a?(Class) && arg < Case
269
+ end
270
+
215
271
  def self.case_or_flow?(arg)
216
- (arg.is_a?(Class) && arg < Case) || arg.is_a?(Cases::Flow)
272
+ case?(arg) || arg.is_a?(Cases::Flow)
217
273
  end
218
274
  end
@@ -5,6 +5,7 @@ require 'set'
5
5
  module Micro
6
6
  class Case
7
7
  class Result
8
+ require 'micro/case/result/wrapper'
8
9
  require 'micro/case/result/transitions'
9
10
 
10
11
  Kind::Types.add(self)
@@ -23,15 +24,26 @@ module Micro
23
24
  alias value data
24
25
 
25
26
  def initialize(transitions_mapper = Transitions::MapEverything)
27
+ enable_transitions = @@transitions_enabled
28
+
29
+ @__is_unknown = true
26
30
  @__accumulated_data = {}
31
+ @__tracked_use_cases = Set.new
27
32
  @__accessible_attributes = {}
28
33
 
29
- enable_transitions = @@transitions_enabled
30
-
31
34
  @__transitions = enable_transitions ? [] : Kind::Empty::ARRAY
32
35
  @__transitions_mapper = transitions_mapper if enable_transitions
33
36
  end
34
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}>"
45
+ end
46
+
35
47
  def to_ary
36
48
  [data, type]
37
49
  end
@@ -57,7 +69,7 @@ module Micro
57
69
  end
58
70
 
59
71
  def slice(*keys)
60
- Utils.slice_hash(data, keys)
72
+ Utils::Hashes.slice(data, keys)
61
73
  end
62
74
 
63
75
  def success?
@@ -68,6 +80,10 @@ module Micro
68
80
  !success?
69
81
  end
70
82
 
83
+ def unknown?
84
+ @__is_unknown
85
+ end
86
+
71
87
  def accessible_attributes
72
88
  @__accessible_attributes.keys
73
89
  end
@@ -75,6 +91,7 @@ module Micro
75
91
  def on_success(expected_type = nil)
76
92
  return self unless __success_type?(expected_type)
77
93
 
94
+ @__is_unknown = false
78
95
  hook_data = expected_type.nil? ? self : data
79
96
 
80
97
  yield(hook_data, @use_case)
@@ -85,6 +102,7 @@ module Micro
85
102
  def on_failure(expected_type = nil)
86
103
  return self unless __failure_type?(expected_type)
87
104
 
105
+ @__is_unknown = false
88
106
  hook_data = expected_type.nil? ? self : data
89
107
 
90
108
  yield(hook_data, @use_case)
@@ -102,6 +120,14 @@ module Micro
102
120
  self
103
121
  end
104
122
 
123
+ def on_unknown
124
+ return self unless unknown?
125
+
126
+ yield(self, @use_case)
127
+
128
+ self
129
+ end
130
+
105
131
  def then(use_case = nil, attributes = nil, &block)
106
132
  can_yield_self = respond_to?(:yield_self)
107
133
 
@@ -163,9 +189,13 @@ module Micro
163
189
 
164
190
  @__accumulated_data.merge!(@data)
165
191
 
166
- use_case_attributes = Utils.symbolize_hash_keys(@use_case.attributes)
192
+ use_case_attributes = Utils::Hashes.symbolize_keys(@use_case.attributes)
167
193
 
168
- __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
169
199
 
170
200
  __set_transition(use_case_attributes) unless @__transitions.frozen?
171
201
 
@@ -175,7 +205,7 @@ module Micro
175
205
  def __set_accessible_attributes__(arg)
176
206
  return arg unless arg.is_a?(Hash)
177
207
 
178
- attributes = Utils.symbolize_hash_keys(arg)
208
+ attributes = Utils::Hashes.symbolize_keys(arg)
179
209
 
180
210
  __update_accessible_attributes(attributes)
181
211
  __fetch_accessible_attributes
@@ -15,4 +15,3 @@ module Micro
15
15
  end
16
16
  end
17
17
  end
18
-
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ class Case
5
+ class Result
6
+ class Wrapper
7
+ attr_reader :output
8
+
9
+ def initialize(result)
10
+ @result = result
11
+ @output = ::Kind::Undefined
12
+
13
+ @__is_unknown = true
14
+ end
15
+
16
+ def failure(type = nil)
17
+ return if @result.success? || !undefined_output?
18
+
19
+ set_output(yield(@result)) if result_type?(type)
20
+ end
21
+
22
+ def success(type = nil)
23
+ return if @result.failure? || !undefined_output?
24
+
25
+ set_output(yield(@result)) if result_type?(type)
26
+ end
27
+
28
+ def unknown
29
+ @output = yield(@result) if @__is_unknown && undefined_output?
30
+ end
31
+
32
+ private
33
+
34
+ def set_output(value)
35
+ @__is_unknown = false
36
+
37
+ @output = value
38
+ end
39
+
40
+ def undefined_output?
41
+ ::Kind::Undefined == @output
42
+ end
43
+
44
+ def result_type?(type)
45
+ type.nil? || @result.type == type
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -8,7 +8,7 @@ module Micro
8
8
  end
9
9
 
10
10
  def __call__
11
- call
11
+ __call_the_use_case_or_its_flow
12
12
  rescue => exception
13
13
  raise exception if Error.by_wrong_usage?(exception)
14
14
 
@@ -3,25 +3,34 @@
3
3
  module Micro
4
4
  class Case
5
5
  module Utils
6
- def self.symbolize_hash_keys(hash)
7
- if Kind::Of::Hash(hash).respond_to?(:transform_keys)
8
- hash.transform_keys { |key| key.to_sym rescue key }
9
- else
6
+
7
+ module Hashes
8
+ def self.respond_to?(hash, method)
9
+ Kind.of(Hash, hash).respond_to?(method)
10
+ end
11
+
12
+ def self.symbolize_keys(hash)
13
+ return hash.transform_keys { |key| key.to_sym rescue key } if respond_to?(hash, :transform_keys)
14
+
10
15
  hash.each_with_object({}) do |(k, v), memo|
11
16
  key = k.to_sym rescue k
12
-
13
17
  memo[key] = v
14
18
  end
15
19
  end
16
- end
17
20
 
18
- def self.slice_hash(hash, keys)
19
- if Kind::Of::Hash(hash).respond_to?(:slice)
20
- hash.slice(*keys)
21
- else
21
+ def self.stringify_keys(hash)
22
+ return hash.transform_keys(&:to_s) if respond_to?(hash, :transform_keys)
23
+
24
+ hash.each_with_object({}) { |(k, v), memo| memo[k.to_s] = v }
25
+ end
26
+
27
+ def self.slice(hash, keys)
28
+ return hash.slice(*keys) if respond_to?(hash, :slice)
29
+
22
30
  hash.select { |key, _value| keys.include?(key) }
23
31
  end
24
32
  end
33
+
25
34
  end
26
35
  end
27
36
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Micro
4
4
  class Case
5
- VERSION = '4.0.0'.freeze
5
+ VERSION = '4.2.2'.freeze
6
6
  end
7
7
  end
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'micro/cases/utils'
4
+ require 'micro/cases/error'
3
5
  require 'micro/cases/flow'
4
6
  require 'micro/cases/safe/flow'
7
+ require 'micro/cases/map'
5
8
 
6
9
  module Micro
7
10
  module Cases
@@ -12,5 +15,9 @@ module Micro
12
15
  def self.safe_flow(args)
13
16
  Safe::Flow.build(args)
14
17
  end
18
+
19
+ def self.map(args)
20
+ Map.build(args)
21
+ end
15
22
  end
16
23
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ module Cases
5
+
6
+ module Error
7
+ class InvalidUseCases < ArgumentError
8
+ def initialize; super('argument must be a collection of `Micro::Case` classes'.freeze); end
9
+ end
10
+ end
11
+
12
+ end
13
+ end
@@ -3,20 +3,15 @@
3
3
  module Micro
4
4
  module Cases
5
5
  class Flow
6
- class InvalidUseCases < ArgumentError
7
- def initialize; super('argument must be a collection of `Micro::Case` classes'.freeze); end
8
- end
6
+ IsAUseCaseWithDefaults = -> arg { arg.is_a?(Array) && Micro.case?(arg[0]) && arg[1].is_a?(Hash) }
7
+ IsAValidUseCase = -> use_case { Micro.case?(use_case) || IsAUseCaseWithDefaults[use_case] }
9
8
 
10
9
  attr_reader :use_cases
11
10
 
12
- def self.map_use_cases(arg)
13
- arg.is_a?(Flow) ? arg.use_cases : Array(arg)
14
- end
15
-
16
11
  def self.build(args)
17
- use_cases = Array(args).flat_map { |arg| map_use_cases(arg) }
12
+ use_cases = Utils.map_use_cases(args)
18
13
 
19
- raise InvalidUseCases if use_cases.any? { |klass| !(klass < ::Micro::Case) }
14
+ raise Error::InvalidUseCases if use_cases.none?(&IsAValidUseCase)
20
15
 
21
16
  new(use_cases)
22
17
  end
@@ -27,8 +22,12 @@ module Micro
27
22
  @first = @next_ones.shift
28
23
  end
29
24
 
25
+ def inspect
26
+ '#<(%s) use_cases=%s>' % [self.class, @use_cases]
27
+ end
28
+
30
29
  def call!(input:, result:)
31
- first_result = __case_use_case(@first, result, input)
30
+ first_result = __call_use_case(@first, result, input)
32
31
 
33
32
  return first_result if @next_ones.empty?
34
33
 
@@ -36,7 +35,15 @@ module Micro
36
35
  end
37
36
 
38
37
  def call(input = Kind::Empty::HASH)
39
- call!(input: input, result: Case::Result.new)
38
+ result = call!(input: input, result: Case::Result.new)
39
+
40
+ return result unless block_given?
41
+
42
+ result_wrapper = ::Micro::Case::Result::Wrapper.new(result)
43
+
44
+ yield(result_wrapper)
45
+
46
+ result_wrapper.output
40
47
  end
41
48
 
42
49
  alias __call__ call
@@ -68,17 +75,23 @@ module Micro
68
75
  raise Case::Error::InvalidInvocationOfTheThenMethod.new("#{self.class.name}#")
69
76
  end
70
77
 
71
- def __case_use_case(use_case, result, input)
72
- use_case.__new__(result, input).__call__
78
+ def __call_use_case(use_case, result, input)
79
+ __build_use_case(use_case, result, input).__call__
73
80
  end
74
81
 
75
82
  def __call_next_use_cases(first_result)
76
83
  @next_ones.reduce(first_result) do |result, use_case|
77
84
  break result if result.failure?
78
85
 
79
- __case_use_case(use_case, result, result.data)
86
+ __call_use_case(use_case, result, result.data)
80
87
  end
81
88
  end
89
+
90
+ def __build_use_case(use_case, result, input)
91
+ return use_case.__new__(result, input) unless use_case.is_a?(Array)
92
+
93
+ use_case[0].__new__(result, input.merge(use_case[1]))
94
+ end
82
95
  end
83
96
  end
84
97
  end