u-case 2.3.0 → 3.0.0.rc1

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.
@@ -1,39 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'kind'
3
4
  require 'micro/attributes'
4
- # frozen_string_literal: true
5
+
6
+ require 'micro/case/version'
5
7
 
6
8
  module Micro
7
9
  class Case
8
- require 'micro/case/version'
10
+ require 'micro/case/utils'
9
11
  require 'micro/case/result'
10
12
  require 'micro/case/error'
11
13
  require 'micro/case/safe'
12
14
  require 'micro/case/strict'
13
- require 'micro/case/flow/reducer'
14
- require 'micro/case/flow'
15
- require 'micro/case/safe/flow'
15
+
16
+ require 'micro/cases'
16
17
 
17
18
  include Micro::Attributes.without(:strict_initialize)
18
19
 
20
+ def self.call(options = {})
21
+ new(options).call
22
+ end
23
+
19
24
  def self.to_proc
20
25
  Proc.new { |arg| call(arg) }
21
26
  end
22
27
 
23
- def self.Flow(args)
24
- Flow::Reducer.build(Array(args))
28
+ def self.call!
29
+ self
25
30
  end
26
31
 
27
- def self.>>(use_case)
28
- Flow([self, use_case])
32
+ def self.flow(*args)
33
+ @__flow_use_cases = args
29
34
  end
30
35
 
31
- def self.&(use_case)
32
- Safe::Flow([self, use_case])
33
- end
36
+ def self.inherited(subclass)
37
+ subclass.attributes(self.attributes_data({}))
38
+ subclass.extend ::Micro::Attributes.const_get('Macros::ForSubclasses'.freeze)
34
39
 
35
- def self.call(options = {})
36
- new(options).call
40
+ if self.send(:__flow_use_cases) && !subclass.name.to_s.end_with?(FLOW_STEP)
41
+ raise "Wooo, you can't do this! Inherits from a use case which has an inner flow violates "\
42
+ "one of the project principles: Solve complex business logic, by allowing the composition of use cases. "\
43
+ "Instead of doing this, declare a new class/constant with the steps needed.\n\n"\
44
+ "Related issue: https://github.com/serradura/u-case/issues/19\n"
45
+ end
37
46
  end
38
47
 
39
48
  def self.__new__(result, arg)
@@ -42,35 +51,19 @@ module Micro
42
51
  instance
43
52
  end
44
53
 
45
- def self.__call!
46
- return const_get(:Flow_Step) if const_defined?(:Flow_Step)
47
-
48
- const_set(:Flow_Step, Class.new(self) do
49
- private def __call
50
- __call_use_case
51
- end
52
- end)
53
- end
54
+ def self.__call_and_set_transition__(result, arg)
55
+ input =
56
+ arg.is_a?(Hash) ? result.__set_transitions_accessible_attributes__(arg) : arg
54
57
 
55
- def self.call!
56
- self
58
+ __new__(result, input).call
57
59
  end
58
60
 
59
- def self.__flow_reducer
60
- Flow::Reducer
61
+ def self.__flow_builder
62
+ Cases::Flow
61
63
  end
62
64
 
63
65
  def self.__flow_get
64
- @__flow
65
- end
66
-
67
- private_class_method def self.__flow_use_cases_set(args)
68
- @__flow_use_cases = args
69
- end
70
-
71
- private_class_method def self.__flow_use_cases_get
72
- Array(@__flow_use_cases)
73
- .map { |use_case| use_case == self ? self.__call! : use_case }
66
+ return @__flow if defined?(@__flow)
74
67
  end
75
68
 
76
69
  private_class_method def self.__flow_set(args)
@@ -80,15 +73,30 @@ module Micro
80
73
 
81
74
  self.class_eval('def use_cases; self.class.use_cases; end')
82
75
 
83
- @__flow = __flow_reducer.build(args)
76
+ @__flow = __flow_builder.build(args)
84
77
  end
85
78
 
86
- def self.__flow_set!
87
- __flow_set(__flow_use_cases_get) if !__flow_get && @__flow_use_cases
79
+ FLOW_STEP = 'Flow_Step'.freeze
80
+
81
+ private_constant :FLOW_STEP
82
+
83
+ def self.__call!
84
+ return const_get(FLOW_STEP) if const_defined?(FLOW_STEP, false)
85
+
86
+ class_eval("class #{FLOW_STEP} < #{self.name}; private def __call; __call_use_case; end; end")
88
87
  end
89
88
 
90
- def self.flow(*args)
91
- __flow_use_cases_set(args)
89
+ private_class_method def self.__flow_use_cases
90
+ return @__flow_use_cases if defined?(@__flow_use_cases)
91
+ end
92
+
93
+ private_class_method def self.__flow_use_cases_get
94
+ Array(__flow_use_cases)
95
+ .map { |use_case| use_case == self ? self.__call! : use_case }
96
+ end
97
+
98
+ def self.__flow_set!
99
+ __flow_set(__flow_use_cases_get) if !__flow_get && __flow_use_cases
92
100
  end
93
101
 
94
102
  def initialize(input)
@@ -105,7 +113,7 @@ module Micro
105
113
 
106
114
  def __set_result__(result)
107
115
  raise Error::InvalidResultInstance unless result.is_a?(Result)
108
- raise Error::ResultIsAlreadyDefined if @__result
116
+ raise Error::ResultIsAlreadyDefined if defined?(@__result)
109
117
 
110
118
  @__result = result
111
119
  end
@@ -142,29 +150,36 @@ module Micro
142
150
  self.class.__flow_get.call(@__input)
143
151
  end
144
152
 
145
- def Success(arg = :ok)
146
- value, type = block_given? ? [yield, arg] : [arg, :ok]
153
+ def Success(type = :ok, result: nil)
154
+ value = result || type
147
155
 
148
- __get_result__.__set__(true, value, type, nil)
156
+ __get_result(true, value, type)
149
157
  end
150
158
 
151
- def Failure(arg = :error)
152
- value = block_given? ? yield : arg
153
- type = __map_failure_type(value, block_given? ? arg : :error)
159
+ MapFailureType = -> (value, type) do
160
+ return type if type != :error
161
+ return value if value.is_a?(Symbol)
162
+ return :exception if value.is_a?(Exception)
154
163
 
155
- __get_result__.__set__(false, value, type, self)
164
+ type
156
165
  end
157
166
 
158
- def __get_result__
159
- @__result ||= Result.new
167
+ def Failure(type = :error, result: nil)
168
+ value = result || type
169
+
170
+ type = MapFailureType.call(value, type)
171
+
172
+ __get_result(false, value, type)
160
173
  end
161
174
 
162
- def __map_failure_type(arg, type)
163
- return type if type != :error
164
- return arg if arg.is_a?(Symbol)
165
- return :exception if arg.is_a?(Exception)
175
+ def __result__
176
+ @__result ||= Result.new
177
+ end
166
178
 
167
- type
179
+ def __get_result(is_success, value, type)
180
+ __result__.__set__(is_success, value, type, self)
168
181
  end
182
+
183
+ private_constant :MapFailureType
169
184
  end
170
185
  end
@@ -17,6 +17,21 @@ module Micro
17
17
  def initialize; super('type must be a Symbol'.freeze); end
18
18
  end
19
19
 
20
+ class InvalidResultData < TypeError
21
+ end
22
+
23
+ class InvalidSuccessResult < InvalidResultData
24
+ def initialize(object)
25
+ super("Success(result: #{object.inspect}) must be a Hash or Symbol")
26
+ end
27
+ end
28
+
29
+ class InvalidFailureResult < InvalidResultData
30
+ def initialize(object)
31
+ super("Failure(result: #{object.inspect}) must be a Hash, Symbol or an Exception")
32
+ end
33
+ end
34
+
20
35
  class InvalidResultInstance < ArgumentError
21
36
  def initialize; super('argument must be an instance of Micro::Case::Result'.freeze); end
22
37
  end
@@ -25,26 +40,16 @@ module Micro
25
40
  def initialize; super('use case must be a kind or an instance of Micro::Case'.freeze); end
26
41
  end
27
42
 
28
- class InvalidUseCases < ArgumentError
29
- def initialize; super('argument must be a collection of `Micro::Case` classes'.freeze); end
30
- end
31
-
32
43
  class InvalidInvocationOfTheThenMethod < StandardError
33
44
  def initialize; super('Invalid invocation of the Micro::Case::Result#then method'); end
34
45
  end
35
46
 
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
47
  class InvalidAccessToTheUseCaseObject < StandardError
41
48
  def initialize; super('only a failure result can access its use case object'.freeze); end
42
49
  end
43
50
 
44
- module ByWrongUsage
45
- def self.check(exception)
46
- exception.is_a?(Error::UnexpectedResult) || exception.is_a?(ArgumentError)
47
- end
51
+ def self.by_wrong_usage?(exception)
52
+ exception.is_a?(InvalidResultData) || exception.is_a?(Error::UnexpectedResult) || exception.is_a?(ArgumentError)
48
53
  end
49
54
  end
50
55
  end
@@ -1,29 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'set'
4
+
3
5
  module Micro
4
6
  class Case
5
7
  class Result
6
- class Data
7
- attr_reader :value, :type
8
+ Kind::Types.add(self)
8
9
 
9
- def initialize(value, type)
10
- @value, @type = value, type
11
- end
10
+ @@transition_tracking_disabled = false
12
11
 
13
- def to_ary; [value, type]; end
12
+ def self.disable_transition_tracking
13
+ @@transition_tracking_disabled = true
14
14
  end
15
15
 
16
- private_constant :Data
16
+ attr_reader :type, :data
17
17
 
18
- attr_reader :value, :type
18
+ alias_method :value, :data
19
19
 
20
- def __set__(is_success, value, type, use_case)
21
- raise Error::InvalidResultType unless type.is_a?(Symbol)
22
- raise Error::InvalidUseCase if !is_success && !is_a_use_case?(use_case)
20
+ def initialize
21
+ @__transitions__ = []
22
+ @__transitions_accessible_attributes__ = {}
23
+ end
23
24
 
24
- @success, @value, @type, @use_case = is_success, value, type, use_case
25
+ def to_ary
26
+ [data, type]
27
+ end
25
28
 
26
- self
29
+ def [](key)
30
+ data[key]
31
+ end
32
+
33
+ def values_at(*keys)
34
+ data.values_at(*keys)
27
35
  end
28
36
 
29
37
  def success?
@@ -41,18 +49,32 @@ module Micro
41
49
  end
42
50
 
43
51
  def on_success(expected_type = nil)
44
- self.tap { yield(value) if success_type?(expected_type) }
52
+ yield(data) if success_type?(expected_type)
53
+
54
+ self
45
55
  end
46
56
 
47
57
  def on_failure(expected_type = nil)
48
58
  return self unless failure_type?(expected_type)
49
59
 
50
- data = expected_type.nil? ? Data.new(value, type).tap(&:freeze) : value
60
+ hook_data = expected_type.nil? ? self : data
61
+
62
+ yield(hook_data, @use_case)
63
+
64
+ self
65
+ end
66
+
67
+ def on_exception(expected_exception = nil)
68
+ return self unless failure_type?(:exception)
51
69
 
52
- self.tap { yield(data, @use_case) }
70
+ if !expected_exception || (Kind.is(Exception, expected_exception) && data.fetch(:exception).is_a?(expected_exception))
71
+ yield(data, @use_case)
72
+ end
73
+
74
+ self
53
75
  end
54
76
 
55
- def then(arg = nil, &block)
77
+ def then(arg = nil, attributes = nil, &block)
56
78
  can_yield_self = respond_to?(:yield_self)
57
79
 
58
80
  if block
@@ -67,10 +89,45 @@ module Micro
67
89
 
68
90
  return self if failure?
69
91
 
70
- arg.call(self.value)
92
+ input = attributes.is_a?(Hash) ? self.data.merge(attributes) : self.data
93
+
94
+ arg.__call_and_set_transition__(self, input)
71
95
  end
72
96
  end
73
97
 
98
+ def transitions
99
+ @__transitions__.clone
100
+ end
101
+
102
+ FetchData = -> (data, is_success) do
103
+ return data if data.is_a?(Hash)
104
+ 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
+
109
+ raise Micro::Case::Error.const_get(err), data
110
+ end
111
+
112
+ def __set__(is_success, data, type, use_case)
113
+ raise Error::InvalidResultType unless type.is_a?(Symbol)
114
+ raise Error::InvalidUseCase if !is_a_use_case?(use_case)
115
+
116
+ @success, @type, @use_case = is_success, type, use_case
117
+
118
+ @data = FetchData.call(data, is_success)
119
+
120
+ __set_transition__ unless @@transition_tracking_disabled
121
+
122
+ self
123
+ end
124
+
125
+ def __set_transitions_accessible_attributes__(attributes_data)
126
+ return attributes_data if @@transition_tracking_disabled
127
+
128
+ __set_transitions_accessible_attributes__!(attributes_data)
129
+ end
130
+
74
131
  private
75
132
 
76
133
  def success_type?(expected_type)
@@ -84,6 +141,34 @@ module Micro
84
141
  def is_a_use_case?(arg)
85
142
  (arg.is_a?(Class) && arg < ::Micro::Case) || arg.is_a?(::Micro::Case)
86
143
  end
144
+
145
+ def __set_transitions_accessible_attributes__!(attributes_data)
146
+ attributes = Utils.symbolize_hash_keys(attributes_data)
147
+
148
+ __update_transitions_accessible_attributes__(attributes)
149
+ end
150
+
151
+ def __update_transitions_accessible_attributes__(attributes)
152
+ @__transitions_accessible_attributes__.merge!(attributes)
153
+ @__transitions_accessible_attributes__
154
+ end
155
+
156
+ def __set_transition__
157
+ use_case_class = @use_case.class
158
+ use_case_attributes = Utils.symbolize_hash_keys(@use_case.attributes)
159
+
160
+ __update_transitions_accessible_attributes__(use_case_attributes)
161
+
162
+ result = @success ? :success : :failure
163
+
164
+ @__transitions__ << {
165
+ use_case: { class: use_case_class, attributes: use_case_attributes },
166
+ result => { type: @type, result: data },
167
+ accessible_attributes: @__transitions_accessible_attributes__.keys
168
+ }
169
+ end
170
+
171
+ private_constant :FetchData
87
172
  end
88
173
  end
89
174
  end
@@ -3,12 +3,16 @@
3
3
  module Micro
4
4
  class Case
5
5
  class Safe < ::Micro::Case
6
+ def self.__flow_builder
7
+ Cases::Safe::Flow
8
+ end
9
+
6
10
  def call
7
11
  __call
8
12
  rescue => exception
9
- raise exception if Error::ByWrongUsage.check(exception)
13
+ raise exception if Error.by_wrong_usage?(exception)
10
14
 
11
- Failure(exception)
15
+ Failure(result: exception)
12
16
  end
13
17
  end
14
18
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ class Case
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
10
+ hash.each_with_object({}) do |(k, v), memo|
11
+ key = k.to_sym rescue k
12
+
13
+ memo[key] = v
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end