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.
- checksums.yaml +4 -4
- data/README.md +79 -69
- 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
|
@@ -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
|
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)
|
|
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
|
|
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
|