service_actor 2.0.0 → 3.0.0
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 +52 -22
- data/lib/service_actor/base.rb +6 -3
- data/lib/service_actor/collectionable.rb +32 -0
- data/lib/service_actor/conditionable.rb +4 -2
- data/lib/service_actor/core.rb +0 -24
- data/lib/service_actor/defaultable.rb +6 -5
- data/lib/service_actor/nil_checkable.rb +0 -13
- data/lib/service_actor/playable.rb +2 -1
- data/lib/service_actor/result.rb +2 -11
- data/lib/service_actor/type_checkable.rb +2 -3
- data/lib/service_actor/version.rb +1 -1
- metadata +36 -5
- data/lib/service_actor/success.rb +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ce3e96190aef06e630c4e930ad942876f27b8f0dc6a00b33c0ff2104d9be9be
|
4
|
+
data.tar.gz: 7badaaa07f340dfd66924417d214f070fc50346ec3d9805c4d115757b2845e22
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f186543f09b78804ea899b57bc8c78e174de83fa205d0a36d74b3089a17b1e6e71f2ddc6c285ae673b5e796b69ab76f327879b57ff6d97a95c4473aa62d1f763
|
7
|
+
data.tar.gz: 7ecedf103e0b66a462a3cfb9f97a7bdb9ec1ea349b3fe7e2824ac716d9a68f68d29f7dd3828099d7ab6569ae90eac573112317c97a3619a7f1072856727fd1c3
|
data/README.md
CHANGED
@@ -6,6 +6,8 @@ This Ruby gem lets you move your application logic into into small composable
|
|
6
6
|
service objects. It is a lightweight framework that helps you keep your models
|
7
7
|
and controllers thin.
|
8
8
|
|
9
|
+

|
10
|
+
|
9
11
|
## Contents
|
10
12
|
|
11
13
|
- [Installation](#installation)
|
@@ -21,9 +23,11 @@ and controllers thin.
|
|
21
23
|
- [Rollback](#rollback)
|
22
24
|
- [Lambdas](#lambdas)
|
23
25
|
- [Play conditions](#play-conditions)
|
24
|
-
- [
|
26
|
+
- [Use with Rails](#use-with-rails)
|
25
27
|
- [Testing](#testing)
|
28
|
+
- [Build your own actor](#build-your-own-actor)
|
26
29
|
- [Influences](#influences)
|
30
|
+
- [Thanks](#thanks)
|
27
31
|
- [Development](#development)
|
28
32
|
- [Contributing](#contributing)
|
29
33
|
- [License](#contributing)
|
@@ -59,7 +63,8 @@ SendNotification.call # => <ServiceActor::Result…>
|
|
59
63
|
```
|
60
64
|
|
61
65
|
When called, actors return a Result. Reading and writing to this result allows
|
62
|
-
actors to accept and return multiple arguments. Let's find out how to do that
|
66
|
+
actors to accept and return multiple arguments. Let's find out how to do that
|
67
|
+
and then we'll see how to chain multiple actors togethor.
|
63
68
|
|
64
69
|
### Inputs
|
65
70
|
|
@@ -137,8 +142,21 @@ result = BuildGreeting.call
|
|
137
142
|
|
138
143
|
### Conditions
|
139
144
|
|
140
|
-
You can
|
141
|
-
|
145
|
+
You can ensure an input is included in a collection by using `in` (unreleased
|
146
|
+
yet):
|
147
|
+
|
148
|
+
```rb
|
149
|
+
class Pay < Actor
|
150
|
+
input :currency, in: %w[EUR USD]
|
151
|
+
|
152
|
+
# …
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
This raises an argument error if the input does not match one of the given
|
157
|
+
values.
|
158
|
+
|
159
|
+
You can also add custom conditions with the name of your choice by using `must`:
|
142
160
|
|
143
161
|
```rb
|
144
162
|
class UpdateAdminUser < Actor
|
@@ -151,7 +169,7 @@ class UpdateAdminUser < Actor
|
|
151
169
|
end
|
152
170
|
```
|
153
171
|
|
154
|
-
|
172
|
+
This raises an argument error if the given lambda returns a falsey value.
|
155
173
|
|
156
174
|
### Allow nil
|
157
175
|
|
@@ -210,8 +228,7 @@ end
|
|
210
228
|
This will raise an error in your app with the given data added to the result.
|
211
229
|
|
212
230
|
To test for the success of your actor instead of raising an exception, use
|
213
|
-
`.result` instead of `.call
|
214
|
-
result.
|
231
|
+
`.result` instead of `.call` and call `success?` or `failure?` on the result.
|
215
232
|
|
216
233
|
For example in a Rails controller:
|
217
234
|
|
@@ -246,9 +263,9 @@ class PlaceOrder < Actor
|
|
246
263
|
end
|
247
264
|
```
|
248
265
|
|
249
|
-
This creates a `call` method
|
250
|
-
|
251
|
-
|
266
|
+
This creates a `call` method that will call every actor along the way. Inputs
|
267
|
+
and outputs will go from one actor to the next, all sharing the same result set
|
268
|
+
until it is finally returned.
|
252
269
|
|
253
270
|
### Rollback
|
254
271
|
|
@@ -320,6 +337,19 @@ end
|
|
320
337
|
|
321
338
|
You can use this to trigger an early success.
|
322
339
|
|
340
|
+
## Use with Rails
|
341
|
+
|
342
|
+
The [service_actor-rails](https://github.com/sunny/actor-rails/) gem includes a
|
343
|
+
Rails generator to create an actor and its spec.
|
344
|
+
|
345
|
+
## Testing
|
346
|
+
|
347
|
+
In your application, add automated testing to your actors as you would do to any
|
348
|
+
other part of your applications.
|
349
|
+
|
350
|
+
You will find that cutting your business logic into single purpose actors will
|
351
|
+
make it easier for you to test your application.
|
352
|
+
|
323
353
|
## Build your own actor
|
324
354
|
|
325
355
|
If you application already uses an "Actor" class, you can build your own by
|
@@ -337,14 +367,6 @@ class ApplicationActor
|
|
337
367
|
end
|
338
368
|
```
|
339
369
|
|
340
|
-
## Testing
|
341
|
-
|
342
|
-
In your application, add automated testing to your actors as you would do to any
|
343
|
-
other part of your applications.
|
344
|
-
|
345
|
-
You will find that cutting your business logic into single purpose actors makes
|
346
|
-
your application much simpler to test.
|
347
|
-
|
348
370
|
## Influences
|
349
371
|
|
350
372
|
This gem is heavily influenced by
|
@@ -367,9 +389,16 @@ However there are a few key differences which make `actor` unique:
|
|
367
389
|
- No `before`, `after` and `around` hooks, prefer using `play` with lambdas or
|
368
390
|
overriding `call`.
|
369
391
|
|
392
|
+
Actor supports mixing actors & interactors when using `play` for a smooth
|
393
|
+
migration.
|
394
|
+
|
395
|
+
## Thanks
|
396
|
+
|
370
397
|
Thank you to @nicoolas25, @AnneSottise & @williampollet for the early thoughts
|
371
398
|
and feedback on this gem.
|
372
399
|
|
400
|
+
Photo by [Lloyd Dirks](https://unsplash.com/photos/4SLz_RCk6kQ).
|
401
|
+
|
373
402
|
## Development
|
374
403
|
|
375
404
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
@@ -377,9 +406,10 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
377
406
|
interactive prompt.
|
378
407
|
|
379
408
|
To release a new version, update the version number in `version.rb`, and in the
|
380
|
-
`CHANGELOG.md
|
381
|
-
|
382
|
-
|
409
|
+
`CHANGELOG.md`. Update the `README.md` if there are missing segments, make sure
|
410
|
+
tests and linting are pristine by calling `rake`, then create a commit for this
|
411
|
+
version. You can then run `rake release`, which will create a git tag, push
|
412
|
+
using git, and push the gem to [rubygems.org](https://rubygems.org).
|
383
413
|
|
384
414
|
## Contributing
|
385
415
|
|
@@ -389,7 +419,7 @@ Bug reports and pull requests are welcome
|
|
389
419
|
This project is intended to be a safe, welcoming space for collaboration, and
|
390
420
|
everyone interacting in the project’s codebase and issue tracker is expected to
|
391
421
|
adhere to the [Contributor Covenant code of
|
392
|
-
conduct](https://github.com/sunny/actor/blob/
|
422
|
+
conduct](https://github.com/sunny/actor/blob/main/CODE_OF_CONDUCT.md).
|
393
423
|
|
394
424
|
## License
|
395
425
|
|
data/lib/service_actor/base.rb
CHANGED
@@ -5,32 +5,35 @@ require 'service_actor/core'
|
|
5
5
|
# Exceptions
|
6
6
|
require 'service_actor/error'
|
7
7
|
require 'service_actor/failure'
|
8
|
-
require 'service_actor/success'
|
9
8
|
require 'service_actor/argument_error'
|
10
9
|
|
11
10
|
# Core
|
12
|
-
require 'service_actor/
|
11
|
+
require 'service_actor/core'
|
13
12
|
require 'service_actor/attributable'
|
14
13
|
require 'service_actor/playable'
|
15
|
-
require 'service_actor/
|
14
|
+
require 'service_actor/result'
|
16
15
|
|
17
16
|
# Concerns
|
18
17
|
require 'service_actor/type_checkable'
|
19
18
|
require 'service_actor/nil_checkable'
|
20
19
|
require 'service_actor/conditionable'
|
21
20
|
require 'service_actor/defaultable'
|
21
|
+
require 'service_actor/collectionable'
|
22
22
|
|
23
23
|
module ServiceActor
|
24
24
|
module Base
|
25
25
|
def self.included(base)
|
26
26
|
# Core
|
27
27
|
base.include(Core)
|
28
|
+
base.include(Attributable)
|
29
|
+
base.include(Playable)
|
28
30
|
|
29
31
|
# Concerns
|
30
32
|
base.include(TypeCheckable)
|
31
33
|
base.include(NilCheckable)
|
32
34
|
base.include(Conditionable)
|
33
35
|
base.include(Defaultable)
|
36
|
+
base.include(Collectionable)
|
34
37
|
end
|
35
38
|
end
|
36
39
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ServiceActor
|
4
|
+
# Add checks to your inputs, by specifying what values are authorized
|
5
|
+
# under the "in" key.
|
6
|
+
#
|
7
|
+
# Example:
|
8
|
+
#
|
9
|
+
# class Pay < Actor
|
10
|
+
# input :provider, in: ['MANGOPAY', 'PayPal', 'Stripe']
|
11
|
+
# end
|
12
|
+
module Collectionable
|
13
|
+
def self.included(base)
|
14
|
+
base.prepend(PrependedMethods)
|
15
|
+
end
|
16
|
+
|
17
|
+
module PrependedMethods
|
18
|
+
def _call
|
19
|
+
self.class.inputs.each do |key, options|
|
20
|
+
next unless options[:in]
|
21
|
+
|
22
|
+
next if options[:in].include?(result[key])
|
23
|
+
|
24
|
+
raise ArgumentError,
|
25
|
+
"Input #{key} must be included in #{options[:in].inspect}"
|
26
|
+
end
|
27
|
+
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,8 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ServiceActor
|
4
|
-
# Add checks to your inputs, by calling lambdas with the name of you choice
|
5
|
-
#
|
4
|
+
# Add checks to your inputs, by calling lambdas with the name of you choice
|
5
|
+
# under the "must" key.
|
6
|
+
#
|
7
|
+
# Will raise an error if any check returns a truthy value.
|
6
8
|
#
|
7
9
|
# Example:
|
8
10
|
#
|
data/lib/service_actor/core.rb
CHANGED
@@ -1,13 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ServiceActor
|
4
|
-
# Actors should start with a verb, inherit from Actor and implement a `call`
|
5
|
-
# method.
|
6
4
|
module Core
|
7
5
|
def self.included(base)
|
8
6
|
base.extend(ClassMethods)
|
9
|
-
base.include(Attributable)
|
10
|
-
base.include(Playable)
|
11
7
|
end
|
12
8
|
|
13
9
|
module ClassMethods
|
@@ -18,15 +14,6 @@ module ServiceActor
|
|
18
14
|
result = Result.to_result(options).merge!(arguments)
|
19
15
|
new(result)._call
|
20
16
|
result
|
21
|
-
# DEPRECATED
|
22
|
-
rescue Success
|
23
|
-
result
|
24
|
-
end
|
25
|
-
|
26
|
-
# :nodoc:
|
27
|
-
def call!(**arguments)
|
28
|
-
warn "DEPRECATED: Prefer `#{name}.call` to `#{name}.call!`."
|
29
|
-
call(**arguments)
|
30
17
|
end
|
31
18
|
|
32
19
|
# Call an actor with arguments. Returns the result and does not raise on
|
@@ -61,20 +48,9 @@ module ServiceActor
|
|
61
48
|
# Returns the current context from inside an actor.
|
62
49
|
attr_reader :result
|
63
50
|
|
64
|
-
def context
|
65
|
-
warn "DEPRECATED: Prefer `result.` to `context.` in #{self.class.name}."
|
66
|
-
|
67
|
-
result
|
68
|
-
end
|
69
|
-
|
70
51
|
# Can be called from inside an actor to stop execution and mark as failed.
|
71
52
|
def fail!(**arguments)
|
72
53
|
result.fail!(**arguments)
|
73
54
|
end
|
74
|
-
|
75
|
-
# DEPRECATED
|
76
|
-
def succeed!(**arguments)
|
77
|
-
result.succeed!(**arguments)
|
78
|
-
end
|
79
55
|
end
|
80
56
|
end
|
@@ -20,13 +20,14 @@ module ServiceActor
|
|
20
20
|
self.class.inputs.each do |name, input|
|
21
21
|
next if result.key?(name)
|
22
22
|
|
23
|
-
|
24
|
-
|
23
|
+
if input.key?(:default)
|
24
|
+
default = input[:default]
|
25
|
+
default = default.call if default.respond_to?(:call)
|
26
|
+
result[name] = default
|
27
|
+
next
|
25
28
|
end
|
26
29
|
|
27
|
-
|
28
|
-
default = default.call if default.respond_to?(:call)
|
29
|
-
result[name] = default
|
30
|
+
raise(ArgumentError, "Input #{name} on #{self.class} is missing.")
|
30
31
|
end
|
31
32
|
|
32
33
|
super
|
@@ -27,8 +27,6 @@ module ServiceActor
|
|
27
27
|
|
28
28
|
def check_context_for_nil(definitions, origin:)
|
29
29
|
definitions.each do |name, options|
|
30
|
-
warn_of_deprecated_required_option(options, name, origin)
|
31
|
-
|
32
30
|
next if !result[name].nil? || allow_nil?(options)
|
33
31
|
|
34
32
|
raise ArgumentError,
|
@@ -37,20 +35,9 @@ module ServiceActor
|
|
37
35
|
end
|
38
36
|
end
|
39
37
|
|
40
|
-
def warn_of_deprecated_required_option(options, name, origin)
|
41
|
-
return unless options.key?(:required)
|
42
|
-
|
43
|
-
warn 'DEPRECATED: The "required" option is deprecated. Replace ' \
|
44
|
-
"`#{origin} :#{name}, required: #{options[:required]}` by " \
|
45
|
-
"`#{origin} :#{name}, allow_nil: #{!options[:required]}` in " \
|
46
|
-
"#{self.class}."
|
47
|
-
end
|
48
|
-
|
49
38
|
def allow_nil?(options)
|
50
39
|
if options.key?(:allow_nil)
|
51
40
|
options[:allow_nil]
|
52
|
-
elsif options.key?(:required)
|
53
|
-
!options[:required]
|
54
41
|
elsif options.key?(:default) && options[:default].nil?
|
55
42
|
true
|
56
43
|
elsif options[:type]
|
data/lib/service_actor/result.rb
CHANGED
@@ -3,7 +3,8 @@
|
|
3
3
|
require 'ostruct'
|
4
4
|
|
5
5
|
module ServiceActor
|
6
|
-
# Represents the
|
6
|
+
# Represents the context of an actor, holding the data from both its inputs
|
7
|
+
# and outputs.
|
7
8
|
class Result < OpenStruct
|
8
9
|
def self.to_result(data)
|
9
10
|
return data if data.is_a?(self)
|
@@ -22,16 +23,6 @@ module ServiceActor
|
|
22
23
|
raise Failure, self
|
23
24
|
end
|
24
25
|
|
25
|
-
def succeed!(result = {})
|
26
|
-
warn 'DEPRECATED: Early success with `succeed!` is deprecated in favor ' \
|
27
|
-
'of adding conditions to `play` calls.'
|
28
|
-
|
29
|
-
merge!(result)
|
30
|
-
merge!(failure?: false)
|
31
|
-
|
32
|
-
raise Success, self
|
33
|
-
end
|
34
|
-
|
35
26
|
def success?
|
36
27
|
!failure?
|
37
28
|
end
|
@@ -1,14 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ServiceActor
|
4
|
-
# Adds `type:` checking to inputs and outputs. Accepts
|
4
|
+
# Adds `type:` checking to inputs and outputs. Accepts class names or classes
|
5
5
|
# that should match an ancestor. Also accepts arrays.
|
6
6
|
#
|
7
7
|
# Example:
|
8
8
|
#
|
9
9
|
# class ReduceOrderAmount < Actor
|
10
|
-
# input :order, type: Order
|
11
|
-
# input :coupon, type: 'Coupon'
|
10
|
+
# input :order, type: 'Order'
|
12
11
|
# input :amount, type: [Integer, Float]
|
13
12
|
# input :bonus_applied, type: [TrueClass FalseClass]
|
14
13
|
# 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:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sunny Ripert
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-08-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -80,6 +80,34 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: code-scanning-rubocop
|
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: interactor
|
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'
|
83
111
|
description: Service objects for your application logic
|
84
112
|
email:
|
85
113
|
- sunny@sunfox.org
|
@@ -95,6 +123,7 @@ files:
|
|
95
123
|
- lib/service_actor/argument_error.rb
|
96
124
|
- lib/service_actor/attributable.rb
|
97
125
|
- lib/service_actor/base.rb
|
126
|
+
- lib/service_actor/collectionable.rb
|
98
127
|
- lib/service_actor/conditionable.rb
|
99
128
|
- lib/service_actor/core.rb
|
100
129
|
- lib/service_actor/defaultable.rb
|
@@ -103,7 +132,6 @@ files:
|
|
103
132
|
- lib/service_actor/nil_checkable.rb
|
104
133
|
- lib/service_actor/playable.rb
|
105
134
|
- lib/service_actor/result.rb
|
106
|
-
- lib/service_actor/success.rb
|
107
135
|
- lib/service_actor/type_checkable.rb
|
108
136
|
- lib/service_actor/version.rb
|
109
137
|
homepage: https://github.com/sunny/actor
|
@@ -112,7 +140,7 @@ licenses:
|
|
112
140
|
metadata:
|
113
141
|
homepage_uri: https://github.com/sunny/actor
|
114
142
|
source_code_uri: https://github.com/sunny/actor
|
115
|
-
changelog_uri: https://github.com/sunny/actor/blob/
|
143
|
+
changelog_uri: https://github.com/sunny/actor/blob/main/CHANGELOG.md
|
116
144
|
post_install_message:
|
117
145
|
rdoc_options: []
|
118
146
|
require_paths:
|
@@ -121,7 +149,10 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
121
149
|
requirements:
|
122
150
|
- - ">="
|
123
151
|
- !ruby/object:Gem::Version
|
124
|
-
version:
|
152
|
+
version: 2.3.7
|
153
|
+
- - "<"
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: 2.8.0
|
125
156
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
157
|
requirements:
|
127
158
|
- - ">="
|