service_actor 1.0.0 → 3.1.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 +234 -91
- data/lib/service_actor.rb +3 -83
- data/lib/service_actor/argument_error.rb +6 -0
- data/lib/{actor → service_actor}/attributable.rb +14 -17
- data/lib/service_actor/base.rb +41 -0
- data/lib/service_actor/collectionable.rb +32 -0
- data/lib/service_actor/conditionable.rb +40 -0
- data/lib/service_actor/core.rb +56 -0
- data/lib/service_actor/defaultable.rb +37 -0
- data/lib/service_actor/error.rb +6 -0
- data/lib/service_actor/failable.rb +40 -0
- data/lib/service_actor/failure.rb +16 -0
- data/lib/service_actor/nil_checkable.rb +51 -0
- data/lib/{actor → service_actor}/playable.rb +10 -9
- data/lib/service_actor/result.rb +55 -0
- data/lib/service_actor/type_checkable.rb +51 -0
- data/lib/service_actor/version.rb +5 -0
- metadata +76 -16
- data/lib/actor/conditionable.rb +0 -34
- data/lib/actor/context.rb +0 -92
- data/lib/actor/defaultable.rb +0 -25
- data/lib/actor/failure.rb +0 -16
- data/lib/actor/filtered_context.rb +0 -49
- data/lib/actor/requireable.rb +0 -36
- data/lib/actor/success.rb +0 -6
- data/lib/actor/type_checkable.rb +0 -43
- data/lib/actor/version.rb +0 -5
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
#
|
5
|
-
# rollback on any actor that succeeded.
|
3
|
+
module ServiceActor
|
4
|
+
# Play class method to call a series of actors with the same result. On
|
5
|
+
# failure, calls rollback on any actor that succeeded.
|
6
6
|
#
|
7
7
|
# class CreateUser < Actor
|
8
8
|
# play SaveUser,
|
@@ -38,11 +38,11 @@ class Actor
|
|
38
38
|
module PrependedMethods
|
39
39
|
def call
|
40
40
|
self.class.play_actors.each do |options|
|
41
|
-
next if options[:if] && !options[:if].call(
|
41
|
+
next if options[:if] && !options[:if].call(result)
|
42
42
|
|
43
43
|
play_actor(options[:actor])
|
44
44
|
end
|
45
|
-
rescue
|
45
|
+
rescue Failure
|
46
46
|
rollback
|
47
47
|
raise
|
48
48
|
end
|
@@ -60,11 +60,12 @@ class Actor
|
|
60
60
|
private
|
61
61
|
|
62
62
|
def play_actor(actor)
|
63
|
-
if actor.
|
64
|
-
actor = actor.new(
|
65
|
-
actor.
|
63
|
+
if actor.is_a?(Class) && actor.ancestors.include?(Actor)
|
64
|
+
actor = actor.new(result)
|
65
|
+
actor._call
|
66
66
|
else
|
67
|
-
actor.call(
|
67
|
+
new_result = actor.call(result)
|
68
|
+
result.merge!(new_result.to_h) if new_result.respond_to?(:to_h)
|
68
69
|
end
|
69
70
|
|
70
71
|
(@played ||= []).unshift(actor)
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
module ServiceActor
|
6
|
+
# Represents the context of an actor, holding the data from both its inputs
|
7
|
+
# and outputs.
|
8
|
+
class Result < OpenStruct
|
9
|
+
def self.to_result(data)
|
10
|
+
return data if data.is_a?(self)
|
11
|
+
|
12
|
+
new(data.to_h)
|
13
|
+
end
|
14
|
+
|
15
|
+
def inspect
|
16
|
+
"<#{self.class.name} #{to_h}>"
|
17
|
+
end
|
18
|
+
|
19
|
+
def fail!(result = {})
|
20
|
+
merge!(result)
|
21
|
+
merge!(failure?: true)
|
22
|
+
|
23
|
+
raise Failure, self
|
24
|
+
end
|
25
|
+
|
26
|
+
def success?
|
27
|
+
!failure?
|
28
|
+
end
|
29
|
+
|
30
|
+
def failure?
|
31
|
+
self[:failure?] || false
|
32
|
+
end
|
33
|
+
|
34
|
+
def merge!(result)
|
35
|
+
result.each_pair do |key, value|
|
36
|
+
self[key] = value
|
37
|
+
end
|
38
|
+
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def key?(name)
|
43
|
+
to_h.key?(name)
|
44
|
+
end
|
45
|
+
|
46
|
+
def [](name)
|
47
|
+
to_h[name]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Redefined here to override the method on `Object`.
|
51
|
+
def display
|
52
|
+
to_h.fetch(:display)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ServiceActor
|
4
|
+
# Adds `type:` checking to inputs and outputs. Accepts class names or classes
|
5
|
+
# that should match an ancestor. Also accepts arrays.
|
6
|
+
#
|
7
|
+
# Example:
|
8
|
+
#
|
9
|
+
# class ReduceOrderAmount < Actor
|
10
|
+
# input :order, type: 'Order'
|
11
|
+
# input :amount, type: [Integer, Float]
|
12
|
+
# input :bonus_applied, type: [TrueClass FalseClass]
|
13
|
+
# end
|
14
|
+
module TypeCheckable
|
15
|
+
def self.included(base)
|
16
|
+
base.prepend(PrependedMethods)
|
17
|
+
end
|
18
|
+
|
19
|
+
module PrependedMethods
|
20
|
+
def _call
|
21
|
+
check_type_definitions(self.class.inputs, kind: 'Input')
|
22
|
+
|
23
|
+
super
|
24
|
+
|
25
|
+
check_type_definitions(self.class.outputs, kind: 'Output')
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def check_type_definitions(definitions, kind:)
|
31
|
+
definitions.each do |key, options|
|
32
|
+
type_definition = options[:type] || next
|
33
|
+
value = result[key] || next
|
34
|
+
|
35
|
+
types = types_for_definition(type_definition)
|
36
|
+
next if types.any? { |type| value.is_a?(type) }
|
37
|
+
|
38
|
+
raise ArgumentError,
|
39
|
+
"#{kind} #{key} on #{self.class} must be of type " \
|
40
|
+
"#{types.join(', ')} but was #{value.class}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def types_for_definition(type_definition)
|
45
|
+
Array(type_definition).map do |name|
|
46
|
+
name.is_a?(String) ? Object.const_get(name) : name
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: service_actor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 3.1.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:
|
11
|
+
date: 2021-04-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -66,6 +66,62 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop-rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop-performance
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: code-scanning-rubocop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: interactor
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
69
125
|
description: Service objects for your application logic
|
70
126
|
email:
|
71
127
|
- sunny@sunfox.org
|
@@ -77,25 +133,29 @@ extra_rdoc_files:
|
|
77
133
|
files:
|
78
134
|
- LICENSE.txt
|
79
135
|
- README.md
|
80
|
-
- lib/actor/attributable.rb
|
81
|
-
- lib/actor/conditionable.rb
|
82
|
-
- lib/actor/context.rb
|
83
|
-
- lib/actor/defaultable.rb
|
84
|
-
- lib/actor/failure.rb
|
85
|
-
- lib/actor/filtered_context.rb
|
86
|
-
- lib/actor/playable.rb
|
87
|
-
- lib/actor/requireable.rb
|
88
|
-
- lib/actor/success.rb
|
89
|
-
- lib/actor/type_checkable.rb
|
90
|
-
- lib/actor/version.rb
|
91
136
|
- lib/service_actor.rb
|
137
|
+
- lib/service_actor/argument_error.rb
|
138
|
+
- lib/service_actor/attributable.rb
|
139
|
+
- lib/service_actor/base.rb
|
140
|
+
- lib/service_actor/collectionable.rb
|
141
|
+
- lib/service_actor/conditionable.rb
|
142
|
+
- lib/service_actor/core.rb
|
143
|
+
- lib/service_actor/defaultable.rb
|
144
|
+
- lib/service_actor/error.rb
|
145
|
+
- lib/service_actor/failable.rb
|
146
|
+
- lib/service_actor/failure.rb
|
147
|
+
- lib/service_actor/nil_checkable.rb
|
148
|
+
- lib/service_actor/playable.rb
|
149
|
+
- lib/service_actor/result.rb
|
150
|
+
- lib/service_actor/type_checkable.rb
|
151
|
+
- lib/service_actor/version.rb
|
92
152
|
homepage: https://github.com/sunny/actor
|
93
153
|
licenses:
|
94
154
|
- MIT
|
95
155
|
metadata:
|
96
156
|
homepage_uri: https://github.com/sunny/actor
|
97
157
|
source_code_uri: https://github.com/sunny/actor
|
98
|
-
changelog_uri: https://github.com/sunny/actor/blob/
|
158
|
+
changelog_uri: https://github.com/sunny/actor/blob/main/CHANGELOG.md
|
99
159
|
post_install_message:
|
100
160
|
rdoc_options: []
|
101
161
|
require_paths:
|
@@ -104,14 +164,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
104
164
|
requirements:
|
105
165
|
- - ">="
|
106
166
|
- !ruby/object:Gem::Version
|
107
|
-
version: '
|
167
|
+
version: '2.4'
|
108
168
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
169
|
requirements:
|
110
170
|
- - ">="
|
111
171
|
- !ruby/object:Gem::Version
|
112
172
|
version: '0'
|
113
173
|
requirements: []
|
114
|
-
rubygems_version: 3.1.
|
174
|
+
rubygems_version: 3.1.6
|
115
175
|
signing_key:
|
116
176
|
specification_version: 4
|
117
177
|
summary: Service objects for your application logic
|
data/lib/actor/conditionable.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Actor
|
4
|
-
# Add boolean checks to inputs, by calling lambdas starting with `must*`.
|
5
|
-
#
|
6
|
-
# Example:
|
7
|
-
#
|
8
|
-
# class Pay < Actor
|
9
|
-
# input :provider,
|
10
|
-
# must: {
|
11
|
-
# exist: ->(provider) { PROVIDERS.include?(provider) }
|
12
|
-
# }
|
13
|
-
#
|
14
|
-
# output :user, required: true
|
15
|
-
# end
|
16
|
-
module Conditionable
|
17
|
-
def before
|
18
|
-
super
|
19
|
-
|
20
|
-
self.class.inputs.each do |key, options|
|
21
|
-
next unless options[:must]
|
22
|
-
|
23
|
-
options[:must].each do |name, check|
|
24
|
-
value = @context[key]
|
25
|
-
next if check.call(value)
|
26
|
-
|
27
|
-
name = name.to_s.sub(/^must_/, '')
|
28
|
-
raise ArgumentError,
|
29
|
-
"Input #{key} must #{name} but was #{value.inspect}."
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
data/lib/actor/context.rb
DELETED
@@ -1,92 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Actor
|
4
|
-
# Represents the result of an action.
|
5
|
-
class Context
|
6
|
-
def self.to_context(data)
|
7
|
-
return data if data.is_a?(self)
|
8
|
-
|
9
|
-
new(data)
|
10
|
-
end
|
11
|
-
|
12
|
-
def initialize(data = {})
|
13
|
-
@data = data.dup
|
14
|
-
end
|
15
|
-
|
16
|
-
def ==(other)
|
17
|
-
other.class == self.class && data == other.data
|
18
|
-
end
|
19
|
-
|
20
|
-
def inspect
|
21
|
-
"<ActorContext #{data.inspect}>"
|
22
|
-
end
|
23
|
-
|
24
|
-
def fail!(context = {})
|
25
|
-
merge!(context)
|
26
|
-
data[:failure?] = true
|
27
|
-
raise Actor::Failure, self
|
28
|
-
end
|
29
|
-
|
30
|
-
def succeed!(context = {})
|
31
|
-
merge!(context)
|
32
|
-
data[:failure?] = false
|
33
|
-
raise Actor::Success, self
|
34
|
-
end
|
35
|
-
|
36
|
-
def success?
|
37
|
-
!failure?
|
38
|
-
end
|
39
|
-
|
40
|
-
def failure?
|
41
|
-
data.fetch(:failure?, false)
|
42
|
-
end
|
43
|
-
|
44
|
-
def merge!(context)
|
45
|
-
data.merge!(context)
|
46
|
-
|
47
|
-
self
|
48
|
-
end
|
49
|
-
|
50
|
-
def key?(name)
|
51
|
-
data.key?(name)
|
52
|
-
end
|
53
|
-
|
54
|
-
def [](name)
|
55
|
-
data[name]
|
56
|
-
end
|
57
|
-
|
58
|
-
# Redefined here to override the method on `Object`.
|
59
|
-
def display
|
60
|
-
data.fetch(:display)
|
61
|
-
end
|
62
|
-
|
63
|
-
protected
|
64
|
-
|
65
|
-
attr_reader :data
|
66
|
-
|
67
|
-
private
|
68
|
-
|
69
|
-
# rubocop:disable Style/MethodMissingSuper
|
70
|
-
def method_missing(name, *arguments, **)
|
71
|
-
if name =~ /=$/
|
72
|
-
key = name.to_s.sub('=', '').to_sym
|
73
|
-
data[key] = arguments.first
|
74
|
-
else
|
75
|
-
data[name]
|
76
|
-
end
|
77
|
-
end
|
78
|
-
# rubocop:enable Style/MethodMissingSuper
|
79
|
-
|
80
|
-
def respond_to_missing?(*_arguments)
|
81
|
-
true
|
82
|
-
end
|
83
|
-
|
84
|
-
def context_get(key)
|
85
|
-
data[key]
|
86
|
-
end
|
87
|
-
|
88
|
-
def context_set(key, value)
|
89
|
-
data[key] = value
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
data/lib/actor/defaultable.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Actor
|
4
|
-
# Adds the `default:` option to inputs. Accepts regular values and lambdas.
|
5
|
-
#
|
6
|
-
# Example:
|
7
|
-
#
|
8
|
-
# class MultiplyThing < Actor
|
9
|
-
# input :counter, default: 1
|
10
|
-
# input :multiplier, default: -> { rand(1..10) }
|
11
|
-
# end
|
12
|
-
module Defaultable
|
13
|
-
def before
|
14
|
-
self.class.inputs.each do |name, input|
|
15
|
-
next if !input.key?(:default) || @context.key?(name)
|
16
|
-
|
17
|
-
default = input[:default]
|
18
|
-
default = default.call if default.respond_to?(:call)
|
19
|
-
@context.merge!(name => default)
|
20
|
-
end
|
21
|
-
|
22
|
-
super
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|