interaktor 0.5.1 → 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,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 && @interaktor.class.input_attributes.include?(method_name)
95
- input_args[method_name]
96
- elsif @executed && success? && allowable_success_attributes.include?(method_name)
97
- success_args[method_name]
98
- elsif @executed && 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
data/lib/interaktor.rb CHANGED
@@ -15,9 +15,7 @@ module Interaktor
15
15
  include Hooks
16
16
  include Callable
17
17
 
18
- interaction_class = Class.new(Interaktor::Interaction) do
19
- end
20
-
18
+ interaction_class = Class.new(Interaktor::Interaction)
21
19
  base.const_set(:Interaction, interaction_class)
22
20
  end
23
21
  end
@@ -31,23 +29,13 @@ module Interaktor
31
29
  @interaction = self.class::Interaction.new(self, args)
32
30
  end
33
31
 
34
- # @param args [Hash{Symbol=>Object}]
32
+ # @param args [Hash]
35
33
  def fail!(args = {})
36
- if (disallowed_key = args.keys.find { |k| !self.class.failure_attributes.include?(k.to_sym) })
37
- raise Interaktor::Error::UnknownAttributeError.new(self, disallowed_key)
38
- end
39
-
40
- self.class.validate_failure_schema(args)
41
34
  @interaction.fail!(args)
42
35
  end
43
36
 
44
37
  # @param args [Hash]
45
38
  def success!(args = {})
46
- if (disallowed_key = args.keys.find { |k| !self.class.success_attributes.include?(k.to_sym) })
47
- raise Interaktor::Error::UnknownAttributeError.new(self, disallowed_key)
48
- end
49
-
50
- self.class.validate_success_schema(args)
51
39
  @interaction.success!(args)
52
40
  end
53
41
 
@@ -87,10 +75,6 @@ module Interaktor
87
75
  call
88
76
  end
89
77
 
90
- if self.class.required_success_attributes.any? && !@interaction.success_args
91
- raise Interaktor::Error::MissingExplicitSuccessError.new(self, self.class.required_success_attributes)
92
- end
93
-
94
78
  @interaction.called!(self)
95
79
  end
96
80
  rescue