service_actor 2.0.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![Photo of theater seats](https://user-images.githubusercontent.com/132/78340166-e7567000-7595-11ea-97c0-b3e5da2de7a1.png)
|
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
|
- - ">="
|