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.
@@ -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 { extend ClassMethods }
7
+ base.class_eval do
8
+ extend ClassMethods
9
+ end
12
10
  end
13
11
 
14
12
  module ClassMethods
15
- ####################
16
- # INPUT ATTRIBUTES #
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
- explicitly_optional_attributes = input_schema.info[:keys].reject { |_, info| info[:required] }.keys
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
- explicitly_optional_attributes + optional_attributes_by_exclusion
52
- end
53
- end
21
+ const_set(:InputAttributesModel, klass)
54
22
 
55
- # The complete list of input attributes.
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
- # @param schema [Dry::Schema::Params, nil] a predefined schema object
84
- # @yield a new Dry::Schema::Params definition block
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
- # FAILURE ATTRIBUTES #
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
- # The list of attributes which are not required to be provided when failing
127
- # the interaktor.
128
- #
129
- # @return [Array<Symbol>]
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
- explicitly_optional_attributes = failure_schema.info[:keys].reject { |_, info| info[:required] }.keys
39
+ const_set(:FailureAttributesModel, klass)
146
40
 
147
- explicitly_optional_attributes + optional_attributes_by_exclusion
41
+ klass.check_for_disallowed_attribute_names!
148
42
  end
149
43
  end
150
44
 
151
- # The complete list of failure attributes.
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
- # Get the failure attribute schema. Fall back to an empty schema with a
159
- # configuration that will deny ALL provided attributes - not defining an
160
- # failure schema should mean the interaktor has no failure attributes.
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
- # @param args [Hash]
168
- #
169
- # @return [void]
170
- def validate_failure_schema(args)
171
- return if !failure_schema
53
+ const_set(:SuccessAttributesModel, klass)
172
54
 
173
- if (errors = failure_schema.call(args).errors).any?
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
- run_method = raise_exception ? :run! : :run
316
-
317
- case args
318
- when Hash
319
- if (disallowed_key = args.keys.find { |k| !input_attributes.include?(k.to_sym) })
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<Symbol>]
2
+ # @return [Array<String>]
3
3
  attr_reader :attributes
4
4
 
5
5
  # @param interaktor [Class]
6
- # @param attributes [Array<Symbol>]
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::AttributeError
1
+ class Interaktor::Error::MissingExplicitSuccessError < Interaktor::Error::Base
2
2
  def message
3
- "#{interaktor} interaktor execution finished successfully but requires one or more success parameters to have been provided: #{attributes.join(", ")}"
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 [Symbol]
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 requires a '#{attribute}' input
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 list
18
- it as a required attribute.
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 [Symbol]
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 [Symbol]
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
- attr_reader :input_args, :success_args, :failure_args
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
- @input_args = case input
14
- when Hash
15
- input.transform_keys(&:to_sym)
16
- when Interaction
17
- input
18
- .input_args
19
- .merge(input.success_args || {})
20
- .slice(*(interaktor.class.input_attributes || []))
21
- else
22
- raise ArgumentError, "Invalid input type: #{input.class}"
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
- @failure_args = args.transform_keys(&:to_sym)
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
- def allowable_success_attributes
82
- @interaktor.class.success_attributes
83
- end
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
- def allowable_failure_attributes
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
- if !@executed && input_args.key?(method_name)
95
- input_args[method_name]
96
- elsif success? && allowable_success_attributes.include?(method_name)
97
- success_args[method_name]
98
- elsif failure? && allowable_failure_attributes.include?(method_name)
99
- failure_args[method_name]
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
- input_args.key?(method_name) ||
107
- success_args&.key?(method_name) ||
108
- failure_args&.key?(method_name) ||
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
 
@@ -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 latest_interaction
53
- @interaction.instance_variable_set(:@success_args, latest_interaction.success_args)
54
- end
55
- end
56
-
57
- private
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