u-case 2.5.0 → 3.0.0.rc4

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