u-case 2.4.0 → 3.0.0.rc3

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