interaktor 0.5.0 → 0.6.0.pre
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.
- checksums.yaml +4 -4
- data/README.md +80 -71
- data/interaktor.gemspec +2 -2
- data/lib/interaktor/attributes.rb +24 -0
- data/lib/interaktor/callable.rb +42 -262
- data/lib/interaktor/error/attribute_error.rb +2 -2
- data/lib/interaktor/error/attribute_validation_error.rb +20 -0
- data/lib/interaktor/error/missing_explicit_success_error.rb +7 -2
- data/lib/interaktor/error/organizer_missing_passed_attribute_error.rb +7 -7
- data/lib/interaktor/error/organizer_success_attribute_missing_error.rb +4 -4
- data/lib/interaktor/error/unknown_attribute_error.rb +4 -4
- data/lib/interaktor/interaction.rb +83 -29
- data/lib/interaktor/organizer.rb +7 -35
- data/lib/interaktor.rb +2 -18
- data/spec/interaktor/hooks_spec.rb +357 -363
- data/spec/interaktor/organizer_spec.rb +57 -48
- data/spec/support/helpers.rb +13 -2
- data/spec/support/lint.rb +219 -271
- metadata +11 -11
- data/lib/interaktor/error/attribute_schema_validation_error.rb +0 -54
- data/spec/interaktor/context_spec.rb +0 -187
data/lib/interaktor/callable.rb
CHANGED
|
@@ -1,279 +1,58 @@
|
|
|
1
|
-
require "dry-schema"
|
|
2
|
-
|
|
3
|
-
Dry::Schema.load_extensions(:info)
|
|
4
|
-
|
|
5
1
|
module Interaktor::Callable
|
|
6
2
|
# When the module is included in a class, add the relevant class methods to
|
|
7
3
|
# that class.
|
|
8
4
|
#
|
|
9
5
|
# @param base [Class] the class which is including the module
|
|
10
6
|
def self.included(base)
|
|
11
|
-
base.class_eval
|
|
7
|
+
base.class_eval do
|
|
8
|
+
extend ClassMethods
|
|
9
|
+
end
|
|
12
10
|
end
|
|
13
11
|
|
|
14
12
|
module ClassMethods
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
####################
|
|
18
|
-
|
|
19
|
-
# The list of attributes which are required to be passed in when calling
|
|
20
|
-
# the interaktor.
|
|
21
|
-
#
|
|
22
|
-
# @return [Array<Symbol>]
|
|
23
|
-
def required_input_attributes
|
|
24
|
-
@required_input_attributes ||= input_schema
|
|
25
|
-
.info[:keys]
|
|
26
|
-
.select { |_, info| info[:required] }
|
|
27
|
-
.keys
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# The list of attributes which are not required to be passed in when
|
|
31
|
-
# calling the interaktor.
|
|
32
|
-
#
|
|
33
|
-
# @return [Array<Symbol>]
|
|
34
|
-
def optional_input_attributes
|
|
35
|
-
# Adding an optional attribute with NO predicates with Dry::Schema is
|
|
36
|
-
# sort of a "nothing statement" - the schema can sort of ignore it. The
|
|
37
|
-
# problem is that the optional-with-no-predicate key is not included in
|
|
38
|
-
# the #info results, so we need to find an list of keys elsewhere, find
|
|
39
|
-
# the ones that are listed there but not in the #info results, and find
|
|
40
|
-
# the difference. The result are the keys that are omitted from the #info
|
|
41
|
-
# result because they are optional and have no predicates.
|
|
42
|
-
#
|
|
43
|
-
# See https://github.com/dry-rb/dry-schema/issues/347
|
|
44
|
-
@optional_input_attributes ||= begin
|
|
45
|
-
attributes_in_info = input_schema.info[:keys].keys
|
|
46
|
-
all_attributes = input_schema.key_map.keys.map(&:id)
|
|
47
|
-
optional_attributes_by_exclusion = all_attributes - attributes_in_info
|
|
13
|
+
def input(&block)
|
|
14
|
+
raise "Input block already defined" if defined?(self::InputAttributesModel)
|
|
48
15
|
|
|
49
|
-
|
|
16
|
+
# Define self::InputAttributesModel
|
|
17
|
+
Class.new(Interaktor::Attributes, &block).tap do |klass|
|
|
18
|
+
klass.define_singleton_method(:inspect) { name.to_s }
|
|
19
|
+
klass.define_singleton_method(:to_s) { inspect }
|
|
50
20
|
|
|
51
|
-
|
|
52
|
-
end
|
|
53
|
-
end
|
|
21
|
+
const_set(:InputAttributesModel, klass)
|
|
54
22
|
|
|
55
|
-
|
|
56
|
-
#
|
|
57
|
-
# @return [Array<Symbol>]
|
|
58
|
-
def input_attributes
|
|
59
|
-
required_input_attributes + optional_input_attributes
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Get the input attribute schema. Fall back to an empty schema with a
|
|
63
|
-
# configuration that will deny ALL provided attributes - not defining an
|
|
64
|
-
# input schema should mean the interaktor has no input attributes.
|
|
65
|
-
#
|
|
66
|
-
# @return [Dry::Schema::Params]
|
|
67
|
-
def input_schema
|
|
68
|
-
@input_schema || Dry::Schema.Params
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# @param args [Hash]
|
|
72
|
-
def validate_input_schema(args)
|
|
73
|
-
return if !input_schema
|
|
74
|
-
|
|
75
|
-
if (errors = input_schema.call(args).errors).any?
|
|
76
|
-
raise Interaktor::Error::AttributeSchemaValidationError.new(
|
|
77
|
-
self,
|
|
78
|
-
errors.to_h
|
|
79
|
-
)
|
|
80
|
-
end
|
|
81
|
-
end
|
|
23
|
+
klass.check_for_disallowed_attribute_names!
|
|
82
24
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def input(schema = nil, &block)
|
|
86
|
-
raise "No schema or schema definition block provided to interaktor input." if schema.nil? && !block
|
|
87
|
-
|
|
88
|
-
raise "Provided both a schema and a schema definition block for interaktor input." if schema && block
|
|
89
|
-
|
|
90
|
-
if schema
|
|
91
|
-
raise "Provided argument is not a Dry::Schema::Params object." unless schema.is_a?(Dry::Schema::Params)
|
|
92
|
-
|
|
93
|
-
@input_schema = schema
|
|
94
|
-
elsif block
|
|
95
|
-
@input_schema = Dry::Schema.Params { instance_eval(&block) }
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# define the getters and setters for the input attributes
|
|
99
|
-
@input_schema.key_map.keys.each do |key| # rubocop:disable Style/HashEachMethods
|
|
100
|
-
attribute_name = key.id
|
|
101
|
-
|
|
102
|
-
# Define getter
|
|
103
|
-
define_method(attribute_name) { @interaction.send(attribute_name) }
|
|
104
|
-
|
|
105
|
-
# Define setter
|
|
106
|
-
define_method(:"#{attribute_name}=") do |value|
|
|
107
|
-
@interaction.send(:"#{attribute_name}=", value)
|
|
25
|
+
klass.attribute_names.each do |name|
|
|
26
|
+
define_method(name) { @interaction.send(name) }
|
|
108
27
|
end
|
|
109
28
|
end
|
|
110
29
|
end
|
|
111
30
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
######################
|
|
115
|
-
|
|
116
|
-
# The list of attributes which are required to be provided when failing the
|
|
117
|
-
# interaktor.
|
|
118
|
-
#
|
|
119
|
-
# @return [Array<Symbol>]
|
|
120
|
-
def required_failure_attributes
|
|
121
|
-
@required_failure_attributes ||= failure_schema.info[:keys]
|
|
122
|
-
.select { |_, info| info[:required] }
|
|
123
|
-
.keys
|
|
124
|
-
end
|
|
31
|
+
def failure(&block)
|
|
32
|
+
raise "Failure block already defined" if defined?(self::FailureAttributesModel)
|
|
125
33
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def optional_failure_attributes
|
|
131
|
-
# Adding an optional attribute with NO predicates with Dry::Schema is
|
|
132
|
-
# sort of a "nothing statement" - the schema can sort of ignore it. The
|
|
133
|
-
# problem is that the optional-with-no-predicate key is not included in
|
|
134
|
-
# the #info results, so we need to find an list of keys elsewhere, find
|
|
135
|
-
# the ones that are listed there but not in the #info results, and find
|
|
136
|
-
# the difference. The result are the keys that are omitted from the #info
|
|
137
|
-
# result because they are optional and have no predicates.
|
|
138
|
-
#
|
|
139
|
-
# See https://github.com/dry-rb/dry-schema/issues/347
|
|
140
|
-
@optional_failure_attributes ||= begin
|
|
141
|
-
attributes_in_info = failure_schema.info[:keys].keys
|
|
142
|
-
all_attributes = failure_schema.key_map.keys.map(&:id)
|
|
143
|
-
optional_attributes_by_exclusion = all_attributes - attributes_in_info
|
|
34
|
+
# Define self::FailureAttributesModel
|
|
35
|
+
Class.new(Interaktor::Attributes, &block).tap do |klass|
|
|
36
|
+
klass.define_singleton_method(:inspect) { name.to_s }
|
|
37
|
+
klass.define_singleton_method(:to_s) { inspect }
|
|
144
38
|
|
|
145
|
-
|
|
39
|
+
const_set(:FailureAttributesModel, klass)
|
|
146
40
|
|
|
147
|
-
|
|
41
|
+
klass.check_for_disallowed_attribute_names!
|
|
148
42
|
end
|
|
149
43
|
end
|
|
150
44
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
# @return [Array<Symbol>]
|
|
154
|
-
def failure_attributes
|
|
155
|
-
required_failure_attributes + optional_failure_attributes
|
|
156
|
-
end
|
|
45
|
+
def success(&block)
|
|
46
|
+
raise "Success block already defined" if defined?(self::SuccessAttributesModel)
|
|
157
47
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
# @return [Dry::Schema::Params]
|
|
163
|
-
def failure_schema
|
|
164
|
-
@failure_schema || Dry::Schema.Params
|
|
165
|
-
end
|
|
48
|
+
# Define self::SuccessAttributesModel
|
|
49
|
+
Class.new(Interaktor::Attributes, &block).tap do |klass|
|
|
50
|
+
klass.define_singleton_method(:inspect) { name.to_s }
|
|
51
|
+
klass.define_singleton_method(:to_s) { inspect }
|
|
166
52
|
|
|
167
|
-
|
|
168
|
-
#
|
|
169
|
-
# @return [void]
|
|
170
|
-
def validate_failure_schema(args)
|
|
171
|
-
return if !failure_schema
|
|
53
|
+
const_set(:SuccessAttributesModel, klass)
|
|
172
54
|
|
|
173
|
-
|
|
174
|
-
raise Interaktor::Error::AttributeSchemaValidationError.new(
|
|
175
|
-
self,
|
|
176
|
-
errors.to_h
|
|
177
|
-
)
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
# @param schema [Dry::Schema::Params, nil] a predefined schema object
|
|
182
|
-
# @yield a new Dry::Schema::Params definition block
|
|
183
|
-
def failure(schema = nil, &block)
|
|
184
|
-
raise "No schema or schema definition block provided to interaktor failure method." if schema.nil? && !block
|
|
185
|
-
|
|
186
|
-
raise "Provided both a schema and a schema definition block for interaktor failure method." if schema && block
|
|
187
|
-
|
|
188
|
-
if schema
|
|
189
|
-
raise "Provided argument is not a Dry::Schema::Params object." unless schema.is_a?(Dry::Schema::Params)
|
|
190
|
-
|
|
191
|
-
@failure_schema = schema
|
|
192
|
-
elsif block
|
|
193
|
-
@failure_schema = Dry::Schema.Params { instance_eval(&block) }
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
######################
|
|
198
|
-
# SUCCESS ATTRIBUTES #
|
|
199
|
-
######################
|
|
200
|
-
|
|
201
|
-
# The list of attributes which are required to be provided when the
|
|
202
|
-
# interaktor succeeds.
|
|
203
|
-
#
|
|
204
|
-
# @return [Array<Symbol>]
|
|
205
|
-
def required_success_attributes
|
|
206
|
-
@required_success_attributes ||= success_schema.info[:keys]
|
|
207
|
-
.select { |_, info| info[:required] }
|
|
208
|
-
.keys
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
# The list of attributes which are not required to be provided when failing
|
|
212
|
-
# the interaktor.
|
|
213
|
-
#
|
|
214
|
-
# @return [Array<Symbol>]
|
|
215
|
-
def optional_success_attributes
|
|
216
|
-
# Adding an optional attribute with NO predicates with Dry::Schema is
|
|
217
|
-
# sort of a "nothing statement" - the schema can sort of ignore it. The
|
|
218
|
-
# problem is that the optional-with-no-predicate key is not included in
|
|
219
|
-
# the #info results, so we need to find an list of keys elsewhere, find
|
|
220
|
-
# the ones that are listed there but not in the #info results, and find
|
|
221
|
-
# the difference. The result are the keys that are omitted from the #info
|
|
222
|
-
# result because they are optional and have no predicates.
|
|
223
|
-
#
|
|
224
|
-
# See https://github.com/dry-rb/dry-schema/issues/347
|
|
225
|
-
@optional_success_attributes ||= begin
|
|
226
|
-
attributes_in_info = success_schema.info[:keys].keys
|
|
227
|
-
all_attributes = success_schema.key_map.keys.map(&:id)
|
|
228
|
-
optional_attributes_by_exclusion = all_attributes - attributes_in_info
|
|
229
|
-
|
|
230
|
-
explicitly_optional_attributes = success_schema.info[:keys].reject { |_, info| info[:required] }.keys
|
|
231
|
-
|
|
232
|
-
explicitly_optional_attributes + optional_attributes_by_exclusion
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
# The complete list of success attributes.
|
|
237
|
-
#
|
|
238
|
-
# @return [Array<Symbol>]
|
|
239
|
-
def success_attributes
|
|
240
|
-
required_success_attributes + optional_success_attributes
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
# Get the success attribute schema. Fall back to an empty schema with a
|
|
244
|
-
# configuration that will deny ALL provided attributes - not defining an
|
|
245
|
-
# success schema should mean the interaktor has no success attributes.
|
|
246
|
-
#
|
|
247
|
-
# @return [Dry::Schema::Params]
|
|
248
|
-
def success_schema
|
|
249
|
-
@success_schema || Dry::Schema.Params
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
# @param args [Hash]
|
|
253
|
-
def validate_success_schema(args)
|
|
254
|
-
return if !success_schema
|
|
255
|
-
|
|
256
|
-
if (errors = success_schema.call(args).errors).any?
|
|
257
|
-
raise Interaktor::Error::AttributeSchemaValidationError.new(
|
|
258
|
-
self,
|
|
259
|
-
errors.to_h
|
|
260
|
-
)
|
|
261
|
-
end
|
|
262
|
-
end
|
|
263
|
-
|
|
264
|
-
# @param schema [Dry::Schema::Params, nil] a predefined schema object
|
|
265
|
-
# @yield a new Dry::Schema::Params definition block
|
|
266
|
-
def success(schema = nil, &block)
|
|
267
|
-
raise "No schema or schema definition block provided to interaktor success method." if schema.nil? && !block
|
|
268
|
-
|
|
269
|
-
raise "Provided both a schema and a schema definition block for interaktor success method." if schema && block
|
|
270
|
-
|
|
271
|
-
if schema
|
|
272
|
-
raise "Provided argument is not a Dry::Schema::Params object." unless schema.is_a?(Dry::Schema::Params)
|
|
273
|
-
|
|
274
|
-
@success_schema = schema
|
|
275
|
-
elsif block
|
|
276
|
-
@success_schema = Dry::Schema.Params { instance_eval(&block) }
|
|
55
|
+
klass.check_for_disallowed_attribute_names!
|
|
277
56
|
end
|
|
278
57
|
end
|
|
279
58
|
|
|
@@ -312,22 +91,23 @@ module Interaktor::Callable
|
|
|
312
91
|
#
|
|
313
92
|
# @return [Interaktor::Interaction]
|
|
314
93
|
def execute(args, raise_exception:)
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
raise Interaktor::Error::UnknownAttributeError.new(self, disallowed_key)
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
validate_input_schema(args)
|
|
324
|
-
new(args).tap(&run_method).instance_variable_get(:@interaction)
|
|
325
|
-
when Interaktor::Interaction
|
|
326
|
-
new(args).tap(&run_method).instance_variable_get(:@interaction)
|
|
94
|
+
interaction = case args
|
|
95
|
+
when Hash, Interaktor::Interaction
|
|
96
|
+
new(args)
|
|
97
|
+
.tap(&(raise_exception ? :run! : :run))
|
|
98
|
+
.instance_variable_get(:@interaction)
|
|
327
99
|
else
|
|
328
100
|
raise ArgumentError,
|
|
329
101
|
"Expected a hash argument when calling the interaktor, got a #{args.class} instead."
|
|
330
102
|
end
|
|
103
|
+
|
|
104
|
+
if interaction.success? &&
|
|
105
|
+
!interaction.early_return? &&
|
|
106
|
+
defined?(self::SuccessAttributesModel)
|
|
107
|
+
raise Interaktor::Error::MissingExplicitSuccessError.new(self)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
interaction
|
|
331
111
|
end
|
|
332
112
|
end
|
|
333
113
|
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
class Interaktor::Error::AttributeError < Interaktor::Error::Base
|
|
2
|
-
# @return [Array<
|
|
2
|
+
# @return [Array<String>]
|
|
3
3
|
attr_reader :attributes
|
|
4
4
|
|
|
5
5
|
# @param interaktor [Class]
|
|
6
|
-
# @param attributes [Array<
|
|
6
|
+
# @param attributes [Array<String>]
|
|
7
7
|
def initialize(interaktor, attributes)
|
|
8
8
|
super(interaktor)
|
|
9
9
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class Interaktor::Error::AttributeValidationError < Interaktor::Error::Base
|
|
2
|
+
attr_reader :model
|
|
3
|
+
|
|
4
|
+
# @param model [Object]
|
|
5
|
+
def initialize(interaktor, model)
|
|
6
|
+
super(interaktor)
|
|
7
|
+
|
|
8
|
+
@model = model
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# @return [Hash{Symbol=>Array<String>}]
|
|
12
|
+
def validation_errors
|
|
13
|
+
model.errors.messages
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @return [String]
|
|
17
|
+
def message
|
|
18
|
+
"Interaktor attributes failed validation:\n #{model.errors.full_messages.join("\n ")}"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
class Interaktor::Error::MissingExplicitSuccessError < Interaktor::Error::
|
|
1
|
+
class Interaktor::Error::MissingExplicitSuccessError < Interaktor::Error::Base
|
|
2
2
|
def message
|
|
3
|
-
|
|
3
|
+
<<~MSG.gsub(/\s+/, " ")
|
|
4
|
+
#{interaktor} interaktor execution finished successfully, but the
|
|
5
|
+
interaktor definition includes a `success` attribute definition, and as a
|
|
6
|
+
result the interaktor must call the `success!` method with the appropriate
|
|
7
|
+
attributes.
|
|
8
|
+
MSG
|
|
4
9
|
end
|
|
5
10
|
end
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
class Interaktor::Error::OrganizerMissingPassedAttributeError < Interaktor::Error::AttributeError
|
|
2
|
-
# @return [
|
|
2
|
+
# @return [String]
|
|
3
3
|
attr_reader :attribute
|
|
4
4
|
|
|
5
5
|
# @param next_interaktor [Class]
|
|
6
|
-
# @param attribute [Symbol]
|
|
6
|
+
# @param attribute [Symbol, String]
|
|
7
7
|
def initialize(interaktor, attribute)
|
|
8
|
-
super(interaktor, [attribute])
|
|
8
|
+
super(interaktor, [attribute.to_s])
|
|
9
9
|
|
|
10
|
-
@attribute = attribute
|
|
10
|
+
@attribute = attribute.to_s
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def message
|
|
14
14
|
<<~MESSAGE.strip.tr("\n", " ")
|
|
15
|
-
An organized #{interaktor} interaktor
|
|
15
|
+
An organized #{interaktor} interaktor defines a '#{attribute}' input
|
|
16
16
|
attribute, but none of the interaktors that come before it in the
|
|
17
|
-
organizer list it as a success attribute, and the organizer does not
|
|
18
|
-
it as
|
|
17
|
+
organizer list it as a success attribute, and the organizer does not
|
|
18
|
+
define it as an input attribute.
|
|
19
19
|
MESSAGE
|
|
20
20
|
end
|
|
21
21
|
end
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
class Interaktor::Error::OrganizerSuccessAttributeMissingError < Interaktor::Error::AttributeError
|
|
2
|
-
# @return [
|
|
2
|
+
# @return [String]
|
|
3
3
|
attr_reader :attribute
|
|
4
4
|
|
|
5
5
|
# @param interaktor [Class]
|
|
6
|
-
# @param attribute [Symbol]
|
|
6
|
+
# @param attribute [Symbol, String]
|
|
7
7
|
def initialize(interaktor, attribute)
|
|
8
|
-
super(interaktor, [attribute])
|
|
8
|
+
super(interaktor, [attribute.to_s])
|
|
9
9
|
|
|
10
|
-
@attribute = attribute
|
|
10
|
+
@attribute = attribute.to_s
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def message
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
class Interaktor::Error::UnknownAttributeError < Interaktor::Error::AttributeError
|
|
2
|
-
# @return [
|
|
2
|
+
# @return [String]
|
|
3
3
|
attr_reader :attribute
|
|
4
4
|
|
|
5
5
|
# @param interaktor [Class]
|
|
6
|
-
# @param attribute [Symbol]
|
|
6
|
+
# @param attribute [Symbol, String]
|
|
7
7
|
def initialize(interaktor, attribute)
|
|
8
|
-
super(interaktor, [attribute])
|
|
8
|
+
super(interaktor, [attribute.to_s])
|
|
9
9
|
|
|
10
|
-
@attribute = attribute
|
|
10
|
+
@attribute = attribute.to_s
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def message
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
require "active_model"
|
|
2
|
+
|
|
1
3
|
module Interaktor
|
|
2
4
|
class Interaction
|
|
3
|
-
|
|
5
|
+
# @return [InputAttributesModel, nil]
|
|
6
|
+
attr_reader :input_object
|
|
7
|
+
|
|
8
|
+
# @return [SuccessAttributesModel, nil]
|
|
9
|
+
attr_reader :success_object
|
|
10
|
+
|
|
11
|
+
# @return [FailureAttributesModel, nil]
|
|
12
|
+
attr_reader :failure_object
|
|
4
13
|
|
|
5
14
|
# @param interaktor [Interaktor]
|
|
6
15
|
# @param input [Hash, Interaction]
|
|
@@ -10,16 +19,36 @@ module Interaktor
|
|
|
10
19
|
@failed = false
|
|
11
20
|
@rolled_back = false
|
|
12
21
|
|
|
13
|
-
@
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
@input_object = if defined?(interaktor.class::InputAttributesModel)
|
|
23
|
+
result = interaktor.class::InputAttributesModel.new
|
|
24
|
+
|
|
25
|
+
case input
|
|
26
|
+
when Hash
|
|
27
|
+
input.each do |k, v|
|
|
28
|
+
result.send("#{k}=", v)
|
|
29
|
+
rescue NoMethodError => e
|
|
30
|
+
if e.receiver == result
|
|
31
|
+
raise Interaktor::Error::UnknownAttributeError.new(interaktor, k)
|
|
32
|
+
else
|
|
33
|
+
raise e
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
when Interaction
|
|
37
|
+
(input.input_object&.attributes || {})
|
|
38
|
+
.merge(input.success_object&.attributes || {})
|
|
39
|
+
.slice(*result.attribute_names)
|
|
40
|
+
.each { |k, v| result.send("#{k}=", v) }
|
|
41
|
+
else
|
|
42
|
+
raise ArgumentError, "Invalid input type: #{input.class}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if !result.valid?
|
|
46
|
+
raise Interaktor::Error::AttributeValidationError.new(@interaktor, result)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
result
|
|
50
|
+
elsif input.is_a?(Hash) && !input.empty?
|
|
51
|
+
raise Interaktor::Error::UnknownAttributeError.new(interaktor, input.keys.first)
|
|
23
52
|
end
|
|
24
53
|
end
|
|
25
54
|
|
|
@@ -60,7 +89,23 @@ module Interaktor
|
|
|
60
89
|
|
|
61
90
|
@executed = true
|
|
62
91
|
@failed = true
|
|
63
|
-
|
|
92
|
+
|
|
93
|
+
if defined?(@interaktor.class::FailureAttributesModel)
|
|
94
|
+
@failure_object = @interaktor.class::FailureAttributesModel.new.tap do |obj|
|
|
95
|
+
args.each do |k, v|
|
|
96
|
+
obj.send("#{k}=", v)
|
|
97
|
+
rescue NoMethodError
|
|
98
|
+
raise Interaktor::Error::UnknownAttributeError.new(@interaktor, k)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
if !obj.valid?
|
|
102
|
+
raise Interaktor::Error::AttributeValidationError.new(@interaktor, obj)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
elsif args.any?
|
|
106
|
+
raise Interaktor::Error::UnknownAttributeError.new(@interaktor, args.keys.first)
|
|
107
|
+
end
|
|
108
|
+
|
|
64
109
|
raise Interaktor::Failure, self
|
|
65
110
|
end
|
|
66
111
|
|
|
@@ -74,16 +119,24 @@ module Interaktor
|
|
|
74
119
|
end
|
|
75
120
|
|
|
76
121
|
@executed = true
|
|
77
|
-
@success_args = args.transform_keys(&:to_sym)
|
|
78
|
-
early_return!
|
|
79
|
-
end
|
|
80
122
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
123
|
+
if defined?(@interaktor.class::SuccessAttributesModel)
|
|
124
|
+
@success_object = @interaktor.class::SuccessAttributesModel.new.tap do |obj|
|
|
125
|
+
args.each do |k, v|
|
|
126
|
+
obj.send("#{k}=", v)
|
|
127
|
+
rescue NoMethodError
|
|
128
|
+
raise Interaktor::Error::UnknownAttributeError.new(@interaktor, k)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
if !obj.valid?
|
|
132
|
+
raise Interaktor::Error::AttributeValidationError.new(@interaktor, obj)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
elsif args.any?
|
|
136
|
+
raise Interaktor::Error::UnknownAttributeError.new(@interaktor, args.keys.first)
|
|
137
|
+
end
|
|
84
138
|
|
|
85
|
-
|
|
86
|
-
@interaktor.class.failure_attributes
|
|
139
|
+
early_return!
|
|
87
140
|
end
|
|
88
141
|
|
|
89
142
|
# Only allow access to arguments when appropriate. Input arguments should be
|
|
@@ -91,21 +144,22 @@ module Interaktor
|
|
|
91
144
|
# execution is complete, either the success or failure arguments should be
|
|
92
145
|
# accessible, depending on the outcome.
|
|
93
146
|
def method_missing(method_name, *args, &block)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
147
|
+
method_string = method_name.to_s
|
|
148
|
+
if !@executed && input_object&.attribute_names&.include?(method_string)
|
|
149
|
+
input_object.send(method_string)
|
|
150
|
+
elsif @executed && success? && success_object&.attribute_names&.include?(method_string)
|
|
151
|
+
success_object.send(method_string)
|
|
152
|
+
elsif @executed && failure? && failure_object&.attribute_names&.include?(method_string)
|
|
153
|
+
failure_object.send(method_string)
|
|
100
154
|
else
|
|
101
155
|
super
|
|
102
156
|
end
|
|
103
157
|
end
|
|
104
158
|
|
|
105
159
|
def respond_to_missing?(method_name, include_private = false)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
160
|
+
input_object&.attribute_names&.include?(method_name) ||
|
|
161
|
+
success_object&.attribute_names&.include?(method_name) ||
|
|
162
|
+
failure_object&.attribute_names&.include?(method_name) ||
|
|
109
163
|
super
|
|
110
164
|
end
|
|
111
165
|
|
data/lib/interaktor/organizer.rb
CHANGED
|
@@ -39,8 +39,6 @@ module Interaktor::Organizer
|
|
|
39
39
|
#
|
|
40
40
|
# @return [void]
|
|
41
41
|
def call
|
|
42
|
-
check_attribute_flow_valid
|
|
43
|
-
|
|
44
42
|
latest_interaction = nil
|
|
45
43
|
|
|
46
44
|
self.class.organized.each do |interaktor|
|
|
@@ -49,39 +47,13 @@ module Interaktor::Organizer
|
|
|
49
47
|
end
|
|
50
48
|
end
|
|
51
49
|
|
|
52
|
-
if
|
|
53
|
-
@interaction.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
# @return [void]
|
|
60
|
-
def check_attribute_flow_valid
|
|
61
|
-
interaktors = self.class.organized
|
|
62
|
-
|
|
63
|
-
# @type [Array<Symbol>]
|
|
64
|
-
success_attributes_so_far = []
|
|
65
|
-
|
|
66
|
-
success_attributes_so_far += self.class.required_input_attributes
|
|
67
|
-
|
|
68
|
-
# @param interaktor [Class]
|
|
69
|
-
interaktors.each do |interaktor|
|
|
70
|
-
interaktor.required_input_attributes.each do |required_attr|
|
|
71
|
-
unless success_attributes_so_far.include?(required_attr)
|
|
72
|
-
raise Interaktor::Error::OrganizerMissingPassedAttributeError.new(interaktor, required_attr)
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
success_attributes_so_far += interaktor.success_attributes
|
|
77
|
-
|
|
78
|
-
next unless interaktor == interaktors.last
|
|
79
|
-
|
|
80
|
-
self.class.success_attributes.each do |success_attr|
|
|
81
|
-
unless success_attributes_so_far.include?(success_attr)
|
|
82
|
-
raise Interaktor::Error::OrganizerSuccessAttributeMissingError.new(interaktor, success_attr)
|
|
83
|
-
end
|
|
84
|
-
end
|
|
50
|
+
if defined?(self.class::SuccessAttributesModel)
|
|
51
|
+
@interaction.success!(
|
|
52
|
+
latest_interaction
|
|
53
|
+
.success_object
|
|
54
|
+
&.attributes
|
|
55
|
+
&.slice(*self.class::SuccessAttributesModel.attribute_names) || {}
|
|
56
|
+
)
|
|
85
57
|
end
|
|
86
58
|
end
|
|
87
59
|
end
|