u-case 3.0.0.rc1 → 3.0.0.rc6

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,27 +12,51 @@ module Micro
12
12
  require 'micro/case/error'
13
13
  require 'micro/case/safe'
14
14
  require 'micro/case/strict'
15
+ require 'micro/case/config'
15
16
 
16
17
  require 'micro/cases'
17
18
 
18
19
  include Micro::Attributes.without(:strict_initialize)
19
20
 
20
- def self.call(options = {})
21
- new(options).call
21
+ def self.call(options = Kind::Empty::HASH)
22
+ new(options).__call__
22
23
  end
23
24
 
24
- def self.to_proc
25
- Proc.new { |arg| call(arg) }
25
+ def self.then(use_case = nil, &block)
26
+ can_yield_self = respond_to?(:yield_self)
27
+
28
+ if block
29
+ raise Error::InvalidInvocationOfTheThenMethod if use_case
30
+ raise NotImplementedError if !can_yield_self
31
+
32
+ yield_self(&block)
33
+ else
34
+ return yield_self if !use_case && can_yield_self
35
+
36
+ self.call.then(use_case)
37
+ end
26
38
  end
27
39
 
28
- def self.call!
29
- self
40
+ def self.to_proc
41
+ Proc.new { |arg| call(arg) }
30
42
  end
31
43
 
32
44
  def self.flow(*args)
33
45
  @__flow_use_cases = args
34
46
  end
35
47
 
48
+ class << self
49
+ alias __call__ call
50
+
51
+ def config
52
+ yield(Config.instance)
53
+ end
54
+
55
+ def call!
56
+ self
57
+ end
58
+ end
59
+
36
60
  def self.inherited(subclass)
37
61
  subclass.attributes(self.attributes_data({}))
38
62
  subclass.extend ::Micro::Attributes.const_get('Macros::ForSubclasses'.freeze)
@@ -46,41 +70,36 @@ module Micro
46
70
  end
47
71
 
48
72
  def self.__new__(result, arg)
49
- instance = new(arg)
73
+ input = result.__set_transitions_accessible_attributes__(arg)
74
+
75
+ instance = new(input)
50
76
  instance.__set_result__(result)
51
77
  instance
52
78
  end
53
79
 
54
- def self.__call_and_set_transition__(result, arg)
55
- input =
56
- arg.is_a?(Hash) ? result.__set_transitions_accessible_attributes__(arg) : arg
57
-
58
- __new__(result, input).call
59
- end
60
-
61
- def self.__flow_builder
80
+ def self.__flow_builder__
62
81
  Cases::Flow
63
82
  end
64
83
 
65
- def self.__flow_get
84
+ def self.__flow_get__
66
85
  return @__flow if defined?(@__flow)
67
86
  end
68
87
 
69
88
  private_class_method def self.__flow_set(args)
70
- return if __flow_get
89
+ return if __flow_get__
71
90
 
72
- def self.use_cases; __flow_get.use_cases; end
91
+ def self.use_cases; __flow_get__.use_cases; end
73
92
 
74
93
  self.class_eval('def use_cases; self.class.use_cases; end')
75
94
 
76
- @__flow = __flow_builder.build(args)
95
+ @__flow = __flow_builder__.build(args)
77
96
  end
78
97
 
79
- FLOW_STEP = 'Flow_Step'.freeze
98
+ FLOW_STEP = 'Self'.freeze
80
99
 
81
100
  private_constant :FLOW_STEP
82
101
 
83
- def self.__call!
102
+ def self.__call__!
84
103
  return const_get(FLOW_STEP) if const_defined?(FLOW_STEP, false)
85
104
 
86
105
  class_eval("class #{FLOW_STEP} < #{self.name}; private def __call; __call_use_case; end; end")
@@ -92,11 +111,11 @@ module Micro
92
111
 
93
112
  private_class_method def self.__flow_use_cases_get
94
113
  Array(__flow_use_cases)
95
- .map { |use_case| use_case == self ? self.__call! : use_case }
114
+ .map { |use_case| use_case == self ? self.__call__! : use_case }
96
115
  end
97
116
 
98
- def self.__flow_set!
99
- __flow_set(__flow_use_cases_get) if !__flow_get && __flow_use_cases
117
+ def self.__flow_set__!
118
+ __flow_set(__flow_use_cases_get) if !__flow_get__ && __flow_use_cases
100
119
  end
101
120
 
102
121
  def initialize(input)
@@ -107,8 +126,8 @@ module Micro
107
126
  raise NotImplementedError
108
127
  end
109
128
 
110
- def call
111
- __call
129
+ def __call__
130
+ __call!
112
131
  end
113
132
 
114
133
  def __set_result__(result)
@@ -120,15 +139,19 @@ module Micro
120
139
 
121
140
  private
122
141
 
142
+ # This method was reserved for a new feature
143
+ def call
144
+ end
145
+
123
146
  def __setup_use_case(input)
124
- self.class.__flow_set!
147
+ self.class.__flow_set__!
125
148
 
126
149
  @__input = input
127
150
 
128
151
  self.attributes = input
129
152
  end
130
153
 
131
- def __call
154
+ def __call!
132
155
  return __call_use_case_flow if __call_use_case_flow?
133
156
 
134
157
  __call_use_case
@@ -139,15 +162,15 @@ module Micro
139
162
 
140
163
  return result if result.is_a?(Result)
141
164
 
142
- raise Error::UnexpectedResult.new(self.class)
165
+ raise Error::UnexpectedResult.new("#{self.class.name}#call!")
143
166
  end
144
167
 
145
168
  def __call_use_case_flow?
146
- self.class.__flow_get
169
+ self.class.__flow_get__
147
170
  end
148
171
 
149
172
  def __call_use_case_flow
150
- self.class.__flow_get.call(@__input)
173
+ self.class.__flow_get__.call(@__input)
151
174
  end
152
175
 
153
176
  def Success(type = :ok, result: nil)
@@ -172,14 +195,18 @@ module Micro
172
195
  __get_result(false, value, type)
173
196
  end
174
197
 
175
- def __result__
198
+ def __result
176
199
  @__result ||= Result.new
177
200
  end
178
201
 
179
202
  def __get_result(is_success, value, type)
180
- __result__.__set__(is_success, value, type, self)
203
+ __result.__set__(is_success, value, type, self)
181
204
  end
182
205
 
183
206
  private_constant :MapFailureType
184
207
  end
208
+
209
+ def self.case_or_flow?(arg)
210
+ (arg.is_a?(Class) && arg < Case) || arg.is_a?(Cases::Flow)
211
+ end
185
212
  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_enabled, Kind::Of::Boolean(value)
19
+ )
20
+ end
21
+ end
22
+ end
23
+ end
@@ -4,9 +4,11 @@ module Micro
4
4
  class Case
5
5
  module Error
6
6
  class UnexpectedResult < TypeError
7
- MESSAGE = '#call! must return an instance of Micro::Case::Result'.freeze
7
+ MESSAGE = 'must return an instance of Micro::Case::Result'.freeze
8
8
 
9
- def initialize(klass); super(klass.name + MESSAGE); end
9
+ def initialize(context)
10
+ super("#{context} #{MESSAGE}")
11
+ end
10
12
  end
11
13
 
12
14
  class ResultIsAlreadyDefined < ArgumentError
@@ -17,18 +19,21 @@ module Micro
17
19
  def initialize; super('type must be a Symbol'.freeze); end
18
20
  end
19
21
 
20
- class InvalidResultData < TypeError
21
- end
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."
22
26
 
23
- class InvalidSuccessResult < InvalidResultData
24
- def initialize(object)
25
- super("Success(result: #{object.inspect}) must be a Hash or Symbol")
26
- end
27
- end
27
+ result = is_success ? 'Success'.freeze : 'Failure'.freeze
28
28
 
29
- class InvalidFailureResult < InvalidResultData
30
- def initialize(object)
31
- super("Failure(result: #{object.inspect}) must be a Hash, Symbol or an Exception")
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}")
32
37
  end
33
38
  end
34
39
 
@@ -44,12 +49,8 @@ module Micro
44
49
  def initialize; super('Invalid invocation of the Micro::Case::Result#then method'); end
45
50
  end
46
51
 
47
- class InvalidAccessToTheUseCaseObject < StandardError
48
- def initialize; super('only a failure result can access its use case object'.freeze); end
49
- end
50
-
51
52
  def self.by_wrong_usage?(exception)
52
- exception.is_a?(InvalidResultData) || exception.is_a?(Error::UnexpectedResult) || exception.is_a?(ArgumentError)
53
+ exception.is_a?(InvalidResult) || exception.is_a?(UnexpectedResult) || exception.is_a?(ArgumentError)
53
54
  end
54
55
  end
55
56
  end
@@ -7,19 +7,16 @@ module Micro
7
7
  class Result
8
8
  Kind::Types.add(self)
9
9
 
10
- @@transition_tracking_disabled = false
10
+ @@transition_tracking_enabled = true
11
11
 
12
- def self.disable_transition_tracking
13
- @@transition_tracking_disabled = true
14
- end
15
-
16
- attr_reader :type, :data
12
+ attr_reader :type, :data, :use_case
17
13
 
18
- alias_method :value, :data
14
+ alias value data
19
15
 
20
16
  def initialize
21
- @__transitions__ = []
22
- @__transitions_accessible_attributes__ = {}
17
+ @__transitions = []
18
+ @__transitions_accumulated_data = {}
19
+ @__transitions_accessible_attributes = {}
23
20
  end
24
21
 
25
22
  def to_ary
@@ -34,6 +31,18 @@ module Micro
34
31
  data.values_at(*keys)
35
32
  end
36
33
 
34
+ def key?(key)
35
+ data.key?(key)
36
+ end
37
+
38
+ def value?(value)
39
+ data.value?(value)
40
+ end
41
+
42
+ def slice(*keys)
43
+ Utils.slice_hash(data, keys)
44
+ end
45
+
37
46
  def success?
38
47
  @success
39
48
  end
@@ -42,20 +51,18 @@ module Micro
42
51
  !success?
43
52
  end
44
53
 
45
- def use_case
46
- return @use_case if failure?
54
+ def on_success(expected_type = nil)
55
+ return self unless __success_type?(expected_type)
47
56
 
48
- raise Error::InvalidAccessToTheUseCaseObject
49
- end
57
+ hook_data = expected_type.nil? ? self : data
50
58
 
51
- def on_success(expected_type = nil)
52
- yield(data) if success_type?(expected_type)
59
+ yield(hook_data, @use_case)
53
60
 
54
61
  self
55
62
  end
56
63
 
57
64
  def on_failure(expected_type = nil)
58
- return self unless failure_type?(expected_type)
65
+ return self unless __failure_type?(expected_type)
59
66
 
60
67
  hook_data = expected_type.nil? ? self : data
61
68
 
@@ -65,7 +72,7 @@ module Micro
65
72
  end
66
73
 
67
74
  def on_exception(expected_exception = nil)
68
- return self unless failure_type?(:exception)
75
+ return self unless __failure_type?(:exception)
69
76
 
70
77
  if !expected_exception || (Kind.is(Exception, expected_exception) && data.fetch(:exception).is_a?(expected_exception))
71
78
  yield(data, @use_case)
@@ -74,97 +81,136 @@ module Micro
74
81
  self
75
82
  end
76
83
 
77
- def then(arg = nil, attributes = nil, &block)
84
+ def then(use_case = nil, attributes = nil, &block)
78
85
  can_yield_self = respond_to?(:yield_self)
79
86
 
80
87
  if block
81
- raise Error::InvalidInvocationOfTheThenMethod if arg
88
+ raise Error::InvalidInvocationOfTheThenMethod if use_case
82
89
  raise NotImplementedError if !can_yield_self
83
90
 
84
91
  yield_self(&block)
85
92
  else
86
- return yield_self if !arg && can_yield_self
93
+ return yield_self if !use_case && can_yield_self
94
+ return failure? ? self : __call_proc(use_case, 'then(-> {})'.freeze) if use_case.is_a?(Proc)
95
+ return failure? ? self : __call_method(use_case, attributes) if use_case.is_a?(Method)
87
96
 
88
- raise Error::InvalidInvocationOfTheThenMethod if !is_a_use_case?(arg)
97
+ raise Error::InvalidInvocationOfTheThenMethod unless ::Micro.case_or_flow?(use_case)
89
98
 
90
99
  return self if failure?
91
100
 
92
101
  input = attributes.is_a?(Hash) ? self.data.merge(attributes) : self.data
93
102
 
94
- arg.__call_and_set_transition__(self, input)
103
+ if use_case.is_a?(::Micro::Cases::Flow)
104
+ use_case.call!(input: input, result: self)
105
+ else
106
+ use_case.__new__(self, input).__call__
107
+ end
95
108
  end
96
109
  end
97
110
 
111
+ def |(arg)
112
+ return self if failure?
113
+
114
+ return __call_proc(arg, '| -> {}'.freeze) if arg.is_a?(Proc)
115
+ return __call_method(arg) if arg.is_a?(Method)
116
+
117
+ raise Error::InvalidInvocationOfTheThenMethod unless ::Micro.case_or_flow?(arg)
118
+
119
+ failure? ? self : arg.__new__(self, data).__call__
120
+ end
121
+
98
122
  def transitions
99
- @__transitions__.clone
123
+ @__transitions.clone
100
124
  end
101
125
 
102
- FetchData = -> (data, is_success) do
126
+ FetchData = -> (data) do
103
127
  return data if data.is_a?(Hash)
104
128
  return { data => true } if data.is_a?(Symbol)
105
- return { exception: data } if data.is_a?(Exception)
106
-
107
- err = is_success ? :InvalidSuccessResult : :InvalidFailureResult
108
129
 
109
- raise Micro::Case::Error.const_get(err), data
130
+ { exception: data } if data.is_a?(Exception)
110
131
  end
111
132
 
112
133
  def __set__(is_success, data, type, use_case)
113
134
  raise Error::InvalidResultType unless type.is_a?(Symbol)
114
- raise Error::InvalidUseCase if !is_a_use_case?(use_case)
135
+ raise Error::InvalidUseCase unless use_case.is_a?(::Micro::Case)
115
136
 
116
137
  @success, @type, @use_case = is_success, type, use_case
117
138
 
118
- @data = FetchData.call(data, is_success)
139
+ @data = FetchData.call(data)
140
+
141
+ raise Micro::Case::Error::InvalidResult.new(is_success, type, use_case) unless @data
119
142
 
120
- __set_transition__ unless @@transition_tracking_disabled
143
+ if @@transition_tracking_enabled
144
+ @__transitions_accumulated_data.merge!(@data)
145
+
146
+ __set_transition
147
+ end
121
148
 
122
149
  self
123
150
  end
124
151
 
125
- def __set_transitions_accessible_attributes__(attributes_data)
126
- return attributes_data if @@transition_tracking_disabled
152
+ def __set_transitions_accessible_attributes__(arg)
153
+ return arg unless @@transition_tracking_enabled
127
154
 
128
- __set_transitions_accessible_attributes__!(attributes_data)
155
+ if arg.is_a?(Hash)
156
+ attributes = Utils.symbolize_hash_keys(arg)
157
+
158
+ __update_transitions_accessible_attributes(attributes)
159
+ end
129
160
  end
130
161
 
131
162
  private
132
163
 
133
- def success_type?(expected_type)
134
- success? && (expected_type.nil? || expected_type == type)
164
+ def __call_with_accumulated_data(fn, opt = nil)
165
+ input =
166
+ __update_transitions_accessible_attributes(
167
+ opt ? opt.merge(@__transitions_accumulated_data) : @__transitions_accumulated_data
168
+ )
169
+
170
+ fn.arity.zero? ? fn.call : fn.call(input)
135
171
  end
136
172
 
137
- def failure_type?(expected_type)
138
- failure? && (expected_type.nil? || expected_type == type)
173
+ def __call_proc(fn, expected)
174
+ result = __call_with_accumulated_data(fn)
175
+
176
+ return self if result === self
177
+
178
+ raise Error::UnexpectedResult.new("#{Result.name}##{expected}")
139
179
  end
140
180
 
141
- def is_a_use_case?(arg)
142
- (arg.is_a?(Class) && arg < ::Micro::Case) || arg.is_a?(::Micro::Case)
181
+ def __call_method(methd, attributes = nil)
182
+ result = __call_with_accumulated_data(methd, attributes)
183
+
184
+ return self if result === self
185
+
186
+ raise Error::UnexpectedResult.new("#{use_case.class.name}#method(:#{methd.name})")
143
187
  end
144
188
 
145
- def __set_transitions_accessible_attributes__!(attributes_data)
146
- attributes = Utils.symbolize_hash_keys(attributes_data)
189
+ def __success_type?(expected_type)
190
+ success? && (expected_type.nil? || expected_type == type)
191
+ end
147
192
 
148
- __update_transitions_accessible_attributes__(attributes)
193
+ def __failure_type?(expected_type)
194
+ failure? && (expected_type.nil? || expected_type == type)
149
195
  end
150
196
 
151
- def __update_transitions_accessible_attributes__(attributes)
152
- @__transitions_accessible_attributes__.merge!(attributes)
153
- @__transitions_accessible_attributes__
197
+ def __update_transitions_accessible_attributes(attributes)
198
+ @__transitions_accessible_attributes.merge!(attributes)
199
+ @__transitions_accessible_attributes
154
200
  end
155
201
 
156
- def __set_transition__
202
+ def __set_transition
157
203
  use_case_class = @use_case.class
158
204
  use_case_attributes = Utils.symbolize_hash_keys(@use_case.attributes)
159
205
 
160
- __update_transitions_accessible_attributes__(use_case_attributes)
206
+ __update_transitions_accessible_attributes(use_case_attributes)
161
207
 
162
208
  result = @success ? :success : :failure
163
209
 
164
- @__transitions__ << {
210
+ @__transitions << {
165
211
  use_case: { class: use_case_class, attributes: use_case_attributes },
166
212
  result => { type: @type, result: data },
167
- accessible_attributes: @__transitions_accessible_attributes__.keys
213
+ accessible_attributes: @__transitions_accessible_attributes.keys
168
214
  }
169
215
  end
170
216