service_actor 3.3.0 → 3.4.1
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 +145 -105
- data/lib/service_actor/argument_error.rb +2 -4
- data/lib/service_actor/attributable.rb +39 -41
- data/lib/service_actor/base.rb +15 -33
- data/lib/service_actor/collectionable.rb +58 -22
- data/lib/service_actor/conditionable.rb +63 -28
- data/lib/service_actor/core.rb +52 -45
- data/lib/service_actor/defaultable.rb +65 -26
- data/lib/service_actor/error.rb +2 -4
- data/lib/service_actor/failable.rb +27 -29
- data/lib/service_actor/failure.rb +8 -10
- data/lib/service_actor/nil_checkable.rb +73 -31
- data/lib/service_actor/playable.rb +62 -64
- data/lib/service_actor/raisable.rb +20 -0
- data/lib/service_actor/result.rb +50 -52
- data/lib/service_actor/support/loader.rb +16 -0
- data/lib/service_actor/type_checkable.rb +75 -35
- data/lib/service_actor/version.rb +1 -1
- metadata +32 -2
@@ -1,91 +1,89 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
3
|
+
# Play class method to call a series of actors with the same result. On failure,
|
4
|
+
# calls rollback on actors that succeeded.
|
5
|
+
#
|
6
|
+
# class CreateUser < Actor
|
7
|
+
# play SaveUser,
|
8
|
+
# CreateSettings,
|
9
|
+
# SendWelcomeEmail
|
10
|
+
# end
|
11
|
+
module ServiceActor::Playable
|
12
|
+
def self.included(base)
|
13
|
+
base.extend(ClassMethods)
|
14
|
+
base.prepend(PrependedMethods)
|
15
|
+
end
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
module ClassMethods
|
18
|
+
def play(*actors, **options)
|
19
|
+
play_actors.push(actors: actors, **options)
|
20
|
+
end
|
22
21
|
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
def play_actors
|
23
|
+
@play_actors ||= []
|
24
|
+
end
|
26
25
|
|
27
|
-
|
28
|
-
|
26
|
+
def inherited(child)
|
27
|
+
super
|
29
28
|
|
30
|
-
|
31
|
-
end
|
29
|
+
child.play_actors.concat(play_actors)
|
32
30
|
end
|
31
|
+
end
|
33
32
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
33
|
+
module PrependedMethods
|
34
|
+
def call
|
35
|
+
self.class.play_actors.each do |options|
|
36
|
+
next if options[:if] && !options[:if].call(result)
|
38
37
|
|
39
|
-
|
40
|
-
end
|
41
|
-
rescue Failure
|
42
|
-
rollback
|
43
|
-
raise
|
38
|
+
options[:actors].each { |actor| play_actor(actor) }
|
44
39
|
end
|
40
|
+
rescue self.class.failure_class
|
41
|
+
rollback
|
42
|
+
raise
|
43
|
+
end
|
45
44
|
|
46
|
-
|
47
|
-
|
45
|
+
def rollback
|
46
|
+
return unless defined?(@played_actors)
|
48
47
|
|
49
|
-
|
50
|
-
|
48
|
+
@played_actors.each do |actor|
|
49
|
+
next unless actor.respond_to?(:rollback)
|
51
50
|
|
52
|
-
|
53
|
-
end
|
51
|
+
actor.rollback
|
54
52
|
end
|
53
|
+
end
|
55
54
|
|
56
|
-
|
55
|
+
private
|
57
56
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
57
|
+
def play_actor(actor)
|
58
|
+
play_service_actor(actor) ||
|
59
|
+
play_method(actor) ||
|
60
|
+
play_interactor(actor) ||
|
61
|
+
actor.call(result)
|
62
|
+
end
|
64
63
|
|
65
|
-
|
66
|
-
|
67
|
-
|
64
|
+
def play_service_actor(actor)
|
65
|
+
return unless actor.is_a?(Class)
|
66
|
+
return unless actor.ancestors.include?(ServiceActor::Core)
|
68
67
|
|
69
|
-
|
70
|
-
|
68
|
+
actor = actor.new(result)
|
69
|
+
actor._call
|
71
70
|
|
72
|
-
|
73
|
-
|
71
|
+
(@played_actors ||= []).unshift(actor)
|
72
|
+
end
|
74
73
|
|
75
|
-
|
76
|
-
|
74
|
+
def play_method(actor)
|
75
|
+
return unless actor.is_a?(Symbol)
|
77
76
|
|
78
|
-
|
77
|
+
send(actor)
|
79
78
|
|
80
|
-
|
81
|
-
|
79
|
+
true
|
80
|
+
end
|
82
81
|
|
83
|
-
|
84
|
-
|
85
|
-
|
82
|
+
def play_interactor(actor)
|
83
|
+
return unless actor.is_a?(Class)
|
84
|
+
return unless actor.ancestors.map(&:name).include?("Interactor")
|
86
85
|
|
87
|
-
|
88
|
-
end
|
86
|
+
result.merge!(actor.call(result).to_h)
|
89
87
|
end
|
90
88
|
end
|
91
89
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ServiceActor::Raisable
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def inherited(child)
|
10
|
+
super
|
11
|
+
|
12
|
+
child.argument_error_class =
|
13
|
+
argument_error_class || ServiceActor::ArgumentError
|
14
|
+
|
15
|
+
child.failure_class = failure_class || ServiceActor::Failure
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :argument_error_class, :failure_class
|
19
|
+
end
|
20
|
+
end
|
data/lib/service_actor/result.rb
CHANGED
@@ -2,77 +2,75 @@
|
|
2
2
|
|
3
3
|
require "ostruct"
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
return data if data.is_a?(self)
|
11
|
-
|
12
|
-
new(data.to_h)
|
13
|
-
end
|
5
|
+
# Represents the context of an actor, holding the data from both its inputs
|
6
|
+
# and outputs.
|
7
|
+
class ServiceActor::Result < OpenStruct
|
8
|
+
def self.to_result(data)
|
9
|
+
return data if data.is_a?(self)
|
14
10
|
|
15
|
-
|
16
|
-
|
17
|
-
end
|
11
|
+
new(data.to_h)
|
12
|
+
end
|
18
13
|
|
19
|
-
|
20
|
-
|
21
|
-
|
14
|
+
def inspect
|
15
|
+
"<#{self.class.name} #{to_h}>"
|
16
|
+
end
|
22
17
|
|
23
|
-
|
24
|
-
|
18
|
+
def fail!(failure_class, result = {})
|
19
|
+
merge!(result)
|
20
|
+
merge!(failure?: true)
|
25
21
|
|
26
|
-
|
27
|
-
|
28
|
-
end
|
22
|
+
raise failure_class, self
|
23
|
+
end
|
29
24
|
|
30
|
-
|
31
|
-
|
32
|
-
|
25
|
+
def success?
|
26
|
+
!failure?
|
27
|
+
end
|
33
28
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
end
|
29
|
+
def failure?
|
30
|
+
self[:failure?] || false
|
31
|
+
end
|
38
32
|
|
39
|
-
|
33
|
+
def merge!(result)
|
34
|
+
result.each_pair do |key, value|
|
35
|
+
self[key] = value
|
40
36
|
end
|
41
37
|
|
42
|
-
|
43
|
-
|
44
|
-
end
|
38
|
+
self
|
39
|
+
end
|
45
40
|
|
46
|
-
|
47
|
-
|
48
|
-
|
41
|
+
def key?(name)
|
42
|
+
to_h.key?(name)
|
43
|
+
end
|
49
44
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
45
|
+
def [](name)
|
46
|
+
to_h[name]
|
47
|
+
end
|
54
48
|
|
55
|
-
|
49
|
+
# Defined here to override the method on `Object`.
|
50
|
+
def display
|
51
|
+
to_h.fetch(:display)
|
52
|
+
end
|
56
53
|
|
57
|
-
|
58
|
-
method_name.to_s.end_with?("?") || super
|
59
|
-
end
|
54
|
+
private
|
60
55
|
|
61
|
-
|
62
|
-
|
56
|
+
def respond_to_missing?(method_name, include_private = false)
|
57
|
+
method_name.to_s.end_with?("?") || super
|
58
|
+
end
|
63
59
|
|
64
|
-
|
65
|
-
|
66
|
-
attribute_value = send(attribute.to_sym)
|
60
|
+
def method_missing(symbol, *args)
|
61
|
+
attribute = symbol.to_s.chomp("?")
|
67
62
|
|
68
|
-
|
69
|
-
|
70
|
-
|
63
|
+
if symbol.to_s.end_with?("?") && respond_to?(attribute)
|
64
|
+
define_singleton_method symbol do
|
65
|
+
value = send(attribute.to_sym)
|
71
66
|
|
72
|
-
|
67
|
+
# Same as ActiveSupport’s #present?
|
68
|
+
value.respond_to?(:empty?) ? !value.empty? : !!value
|
73
69
|
end
|
74
70
|
|
75
|
-
|
71
|
+
return send(symbol)
|
76
72
|
end
|
73
|
+
|
74
|
+
super symbol, *args
|
77
75
|
end
|
78
76
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zeitwerk"
|
4
|
+
|
5
|
+
module ServiceActor; end
|
6
|
+
|
7
|
+
lib = File.expand_path("../..", __dir__)
|
8
|
+
|
9
|
+
loader = Zeitwerk::Loader.new
|
10
|
+
loader.tag = "service_actor"
|
11
|
+
loader.inflector = Zeitwerk::GemInflector.new(
|
12
|
+
File.expand_path("service_actor.rb", lib),
|
13
|
+
)
|
14
|
+
loader.push_dir(lib)
|
15
|
+
loader.ignore(__dir__)
|
16
|
+
loader.setup
|
@@ -1,50 +1,90 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
3
|
+
# Adds `type:` checking to inputs and outputs. Accepts class names or classes
|
4
|
+
# that should match an ancestor. Also accepts arrays.
|
5
|
+
#
|
6
|
+
# Example:
|
7
|
+
#
|
8
|
+
# class ReduceOrderAmount < Actor
|
9
|
+
# input :order, type: "Order"
|
10
|
+
# input :amount, type: [Integer, Float]
|
11
|
+
# input :bonus_applied, type: [TrueClass, FalseClass]
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# class ReduceOrderAmount < Actor
|
15
|
+
# input :order, type: { is: Order, message: "Order is required" }
|
16
|
+
# input :amount, type: { is: Integer, message: "Incorrect amount" }
|
17
|
+
#
|
18
|
+
# input :bonus_applied,
|
19
|
+
# type: {
|
20
|
+
# is: [TrueClass, FalseClass],
|
21
|
+
# message: (lambda do |origin:, input_key:, actor:, expected_type:, given_type:| # rubocop:disable Layout/LineLength
|
22
|
+
# "Wrong type \"#{given_type}\" for \"#{input_key}\". " \
|
23
|
+
# "Expected: \"#{expected_type}\""
|
24
|
+
# end)
|
25
|
+
# }
|
26
|
+
# end
|
27
|
+
module ServiceActor::TypeCheckable
|
28
|
+
def self.included(base)
|
29
|
+
base.prepend(PrependedMethods)
|
30
|
+
end
|
31
|
+
|
32
|
+
module PrependedMethods
|
33
|
+
DEFAULT_MESSAGE = lambda do
|
34
|
+
|origin:, input_key:, actor:, expected_type:, given_type:|
|
35
|
+
|
36
|
+
"The \"#{input_key}\" #{origin} on \"#{actor}\" must be of type " \
|
37
|
+
"\"#{expected_type}\" but was \"#{given_type}\""
|
17
38
|
end
|
18
39
|
|
19
|
-
|
20
|
-
def _call
|
21
|
-
check_type_definitions(self.class.inputs, kind: "Input")
|
40
|
+
private_constant :DEFAULT_MESSAGE
|
22
41
|
|
23
|
-
|
42
|
+
def _call
|
43
|
+
check_type_definitions(self.class.inputs, origin: "input")
|
24
44
|
|
25
|
-
|
26
|
-
|
45
|
+
super
|
46
|
+
|
47
|
+
check_type_definitions(self.class.outputs, origin: "output")
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
27
51
|
|
28
|
-
|
52
|
+
def check_type_definitions(definitions, origin:) # rubocop:disable Metrics/MethodLength
|
53
|
+
definitions.each do |key, options|
|
54
|
+
type_definition = options[:type] || next
|
55
|
+
value = result[key] || next
|
29
56
|
|
30
|
-
|
31
|
-
definitions.each do |key, options|
|
32
|
-
type_definition = options[:type] || next
|
33
|
-
value = result[key] || next
|
57
|
+
types, message = define_types_with(type_definition)
|
34
58
|
|
35
|
-
|
36
|
-
next if types.any? { |type| value.is_a?(type) }
|
59
|
+
next if types.any? { |type| value.is_a?(type) }
|
37
60
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
61
|
+
raise_error_with(
|
62
|
+
message,
|
63
|
+
origin: origin,
|
64
|
+
input_key: key,
|
65
|
+
actor: self.class,
|
66
|
+
expected_type: types.join(", "),
|
67
|
+
given_type: value.class,
|
68
|
+
)
|
42
69
|
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def define_types_with(type_definition)
|
73
|
+
if type_definition.is_a?(Hash)
|
74
|
+
type_definition, message =
|
75
|
+
type_definition.values_at(:is, :message)
|
76
|
+
else
|
77
|
+
message = DEFAULT_MESSAGE
|
78
|
+
end
|
79
|
+
|
80
|
+
types = types_for_definition(type_definition)
|
81
|
+
|
82
|
+
[types, message]
|
83
|
+
end
|
43
84
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
85
|
+
def types_for_definition(type_definition)
|
86
|
+
Array(type_definition).map do |name|
|
87
|
+
name.is_a?(String) ? Object.const_get(name) : name
|
48
88
|
end
|
49
89
|
end
|
50
90
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: service_actor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sunny Ripert
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-09-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: zeitwerk
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: rspec
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -100,6 +114,20 @@ dependencies:
|
|
100
114
|
- - ">="
|
101
115
|
- !ruby/object:Gem::Version
|
102
116
|
version: '0'
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: rubocop-rake
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
type: :development
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
103
131
|
- !ruby/object:Gem::Dependency
|
104
132
|
name: code-scanning-rubocop
|
105
133
|
requirement: !ruby/object:Gem::Requirement
|
@@ -152,7 +180,9 @@ files:
|
|
152
180
|
- lib/service_actor/failure.rb
|
153
181
|
- lib/service_actor/nil_checkable.rb
|
154
182
|
- lib/service_actor/playable.rb
|
183
|
+
- lib/service_actor/raisable.rb
|
155
184
|
- lib/service_actor/result.rb
|
185
|
+
- lib/service_actor/support/loader.rb
|
156
186
|
- lib/service_actor/type_checkable.rb
|
157
187
|
- lib/service_actor/version.rb
|
158
188
|
homepage: https://github.com/sunny/actor
|