service_actor 3.0.0 → 3.1.3
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 +54 -35
- data/lib/service_actor/attributable.rb +3 -0
- data/lib/service_actor/base.rb +3 -3
- data/lib/service_actor/collectionable.rb +2 -1
- data/lib/service_actor/conditionable.rb +1 -1
- data/lib/service_actor/core.rb +4 -1
- data/lib/service_actor/defaultable.rb +1 -1
- data/lib/service_actor/error.rb +1 -1
- data/lib/service_actor/failable.rb +40 -0
- data/lib/service_actor/nil_checkable.rb +5 -10
- data/lib/service_actor/playable.rb +19 -8
- data/lib/service_actor/result.rb +2 -2
- data/lib/service_actor/version.rb +1 -1
- metadata +20 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 40bbf17640409642508a6cb2dc7bd7197a49266ae9639c4a97c5cf52a3696ddd
|
4
|
+
data.tar.gz: 6b2627086780fcbead6e4ce3feff3565eab1bc5e3181a743296e8a7099e22480
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0af13a24439bbab76ce2ae6ddf57e41b86b34c4f81e7d333650675195ab27d2519385e479bc1c41eee3c61f54253812c7e6579fea47bd7d8d17839d76a9808af
|
7
|
+
data.tar.gz: d25a43b1637a9dd1d79c6c0686ccc9bc2bb69dfbf0667c5a8775b1ac3fffab4c3d2233a955a17d676a0ee17b1af1a583449ac39dea220bbc02df3f82e9df6275
|
data/README.md
CHANGED
@@ -18,12 +18,11 @@ and controllers thin.
|
|
18
18
|
- [Conditions](#conditions)
|
19
19
|
- [Allow nil](#allow-nil)
|
20
20
|
- [Types](#types)
|
21
|
-
- [
|
21
|
+
- [Fail](#fail)
|
22
22
|
- [Play actors in a sequence](#play-actors-in-a-sequence)
|
23
23
|
- [Rollback](#rollback)
|
24
24
|
- [Lambdas](#lambdas)
|
25
25
|
- [Play conditions](#play-conditions)
|
26
|
-
- [Use with Rails](#use-with-rails)
|
27
26
|
- [Testing](#testing)
|
28
27
|
- [Build your own actor](#build-your-own-actor)
|
29
28
|
- [Influences](#influences)
|
@@ -34,13 +33,21 @@ and controllers thin.
|
|
34
33
|
|
35
34
|
## Installation
|
36
35
|
|
37
|
-
Add these lines to your application
|
36
|
+
Add these lines to your application’s Gemfile:
|
38
37
|
|
39
38
|
```rb
|
40
39
|
# Composable service objects
|
41
40
|
gem 'service_actor'
|
42
41
|
```
|
43
42
|
|
43
|
+
When using Rails, you can include the
|
44
|
+
[service_actor-rails](https://github.com/sunny/actor-rails) gem:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
# Composable service objects
|
48
|
+
gem "service_actor-rails"
|
49
|
+
```
|
50
|
+
|
44
51
|
## Usage
|
45
52
|
|
46
53
|
Actors are single-purpose actions in your application that represent your
|
@@ -96,7 +103,7 @@ class BuildGreeting < Actor
|
|
96
103
|
output :greeting
|
97
104
|
|
98
105
|
def call
|
99
|
-
self.greeting =
|
106
|
+
self.greeting = "Have a wonderful day!"
|
100
107
|
end
|
101
108
|
end
|
102
109
|
```
|
@@ -115,8 +122,8 @@ Inputs can be marked as optional by providing a default:
|
|
115
122
|
```rb
|
116
123
|
class BuildGreeting < Actor
|
117
124
|
input :name
|
118
|
-
input :adjective, default:
|
119
|
-
input :length_of_time, default: -> { [
|
125
|
+
input :adjective, default: "wonderful"
|
126
|
+
input :length_of_time, default: -> { ["day", "week", "month"].sample }
|
120
127
|
|
121
128
|
output :greeting
|
122
129
|
|
@@ -129,7 +136,7 @@ end
|
|
129
136
|
This lets you call the actor without specifying those keys:
|
130
137
|
|
131
138
|
```rb
|
132
|
-
result = BuildGreeting.call(name:
|
139
|
+
result = BuildGreeting.call(name: "Jim")
|
133
140
|
result.greeting # => "Have a wonderful week Jim!"
|
134
141
|
```
|
135
142
|
|
@@ -142,8 +149,7 @@ result = BuildGreeting.call
|
|
142
149
|
|
143
150
|
### Conditions
|
144
151
|
|
145
|
-
You can ensure an input is included in a collection by using `in
|
146
|
-
yet):
|
152
|
+
You can ensure an input is included in a collection by using `in`:
|
147
153
|
|
148
154
|
```rb
|
149
155
|
class Pay < Actor
|
@@ -185,12 +191,12 @@ end
|
|
185
191
|
|
186
192
|
### Types
|
187
193
|
|
188
|
-
Sometimes it can help to have a quick way of making sure we didn
|
194
|
+
Sometimes it can help to have a quick way of making sure we didn’t mess up our
|
189
195
|
inputs.
|
190
196
|
|
191
197
|
For that you can use the `type` option and giving a class or an array
|
192
|
-
of possible classes. If the input or output doesn
|
193
|
-
|
198
|
+
of possible classes. If the input or output doesn’t match these types, an
|
199
|
+
error is raised.
|
194
200
|
|
195
201
|
```rb
|
196
202
|
class UpdateUser < Actor
|
@@ -205,10 +211,9 @@ You may also use strings instead of constants, such as `type: 'User'`.
|
|
205
211
|
|
206
212
|
When using a type condition, `allow_nil` defaults to `false`.
|
207
213
|
|
208
|
-
###
|
214
|
+
### Fail
|
209
215
|
|
210
|
-
|
211
|
-
mark an actor as having failed, use `fail!`:
|
216
|
+
To stop the execution and mark an actor as having failed, use `fail!`:
|
212
217
|
|
213
218
|
```rb
|
214
219
|
class UpdateUser
|
@@ -218,7 +223,7 @@ class UpdateUser
|
|
218
223
|
def call
|
219
224
|
user.attributes = attributes
|
220
225
|
|
221
|
-
fail!(error:
|
226
|
+
fail!(error: "Invalid user") unless user.valid?
|
222
227
|
|
223
228
|
# …
|
224
229
|
end
|
@@ -228,7 +233,8 @@ end
|
|
228
233
|
This will raise an error in your app with the given data added to the result.
|
229
234
|
|
230
235
|
To test for the success of your actor instead of raising an exception, use
|
231
|
-
`.result` instead of `.call
|
236
|
+
`.result` instead of `.call`. You can then call `success?` or `failure?` on
|
237
|
+
the result.
|
232
238
|
|
233
239
|
For example in a Rails controller:
|
234
240
|
|
@@ -246,7 +252,7 @@ class UsersController < ApplicationController
|
|
246
252
|
end
|
247
253
|
```
|
248
254
|
|
249
|
-
|
255
|
+
The keys you add to `fail!` will be added to the result, for example you could
|
250
256
|
do: `fail!(error_type: "validation", error_code: "uv52", …)`.
|
251
257
|
|
252
258
|
## Play actors in a sequence
|
@@ -295,8 +301,8 @@ anything to clean up if they call `fail!`.
|
|
295
301
|
|
296
302
|
### Lambdas
|
297
303
|
|
298
|
-
You can use inline actions using lambdas. Inside these lambdas
|
299
|
-
|
304
|
+
You can use inline actions using lambdas. Inside these lambdas you have access to
|
305
|
+
the shared result:
|
300
306
|
|
301
307
|
```rb
|
302
308
|
class Pay < Actor
|
@@ -308,15 +314,15 @@ end
|
|
308
314
|
```
|
309
315
|
|
310
316
|
Like in this example, lambdas can be useful for small work or preparing the
|
311
|
-
result for the next actors. If you want to do more work before, or
|
312
|
-
whole `play`, you can also override the `call` method. For example:
|
317
|
+
result for the next actors. If you want to do more work before, after or around
|
318
|
+
the whole `play`, you can also override the `call` method. For example:
|
313
319
|
|
314
320
|
```rb
|
315
321
|
class Pay < Actor
|
316
322
|
# …
|
317
323
|
|
318
324
|
def call
|
319
|
-
Time.with_timezone(
|
325
|
+
Time.with_timezone("Paris") do
|
320
326
|
super
|
321
327
|
end
|
322
328
|
end
|
@@ -337,10 +343,21 @@ end
|
|
337
343
|
|
338
344
|
You can use this to trigger an early success.
|
339
345
|
|
340
|
-
|
346
|
+
### Fail on argument error
|
341
347
|
|
342
|
-
|
343
|
-
|
348
|
+
By default, errors on inputs will raise an error, even when using `.result`
|
349
|
+
instead of `.call`. If instead you want to mark the actor as failed, you can
|
350
|
+
catch the exception to treat it as an actor failure:
|
351
|
+
|
352
|
+
```rb
|
353
|
+
class PlaceOrder < Actor
|
354
|
+
fail_on ServiceActor::ArgumentError
|
355
|
+
|
356
|
+
input :currency, in: ["EUR", "USD"]
|
357
|
+
|
358
|
+
# …
|
359
|
+
end
|
360
|
+
```
|
344
361
|
|
345
362
|
## Testing
|
346
363
|
|
@@ -352,11 +369,11 @@ make it easier for you to test your application.
|
|
352
369
|
|
353
370
|
## Build your own actor
|
354
371
|
|
355
|
-
If you application already uses
|
356
|
-
changing the gem
|
372
|
+
If you application already uses a class called “Actor”, you can build your own
|
373
|
+
by changing the gem’s require statement:
|
357
374
|
|
358
375
|
```rb
|
359
|
-
gem
|
376
|
+
gem "service_actor", require: "service_actor/base"
|
360
377
|
```
|
361
378
|
|
362
379
|
And building your own class to inherit from:
|
@@ -371,7 +388,7 @@ end
|
|
371
388
|
|
372
389
|
This gem is heavily influenced by
|
373
390
|
[Interactor](https://github.com/collectiveidea/interactor) ♥.
|
374
|
-
|
391
|
+
Some key differences make Actor unique:
|
375
392
|
|
376
393
|
- Does not [hide errors when an actor fails inside another
|
377
394
|
actor](https://github.com/collectiveidea/interactor/issues/170).
|
@@ -381,7 +398,7 @@ However there are a few key differences which make `actor` unique:
|
|
381
398
|
and failures are not hidden away because you forgot to use `!`.
|
382
399
|
- Allows defaults, type checking, requirements and conditions on inputs.
|
383
400
|
- Delegates methods on the context: `foo` vs `context.foo`, `self.foo =` vs
|
384
|
-
`context.foo = `, fail!` vs `context.fail!`.
|
401
|
+
`context.foo = `, `fail!` vs `context.fail!`.
|
385
402
|
- Shorter setup syntax: inherit from `< Actor` vs having to `include Interactor`
|
386
403
|
and `include Interactor::Organizer`.
|
387
404
|
- Organizers allow lambdas, being called multiple times, and having conditions.
|
@@ -402,14 +419,16 @@ Photo by [Lloyd Dirks](https://unsplash.com/photos/4SLz_RCk6kQ).
|
|
402
419
|
## Development
|
403
420
|
|
404
421
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
405
|
-
`rake` to run the tests and linting. You can also run `bin/console` for an
|
422
|
+
`bin/rake` to run the tests and linting. You can also run `bin/console` for an
|
406
423
|
interactive prompt.
|
407
424
|
|
408
425
|
To release a new version, update the version number in `version.rb`, and in the
|
409
426
|
`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
|
411
|
-
|
412
|
-
|
427
|
+
tests and linting are pristine by calling `bundle && bin/rake`, then create a
|
428
|
+
commit for this version.
|
429
|
+
|
430
|
+
You can then run `rake release`, which will assign a git tag, push using git,
|
431
|
+
and push the gem to [rubygems.org](https://rubygems.org).
|
413
432
|
|
414
433
|
## Contributing
|
415
434
|
|
data/lib/service_actor/base.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'service_actor/core'
|
4
|
-
|
5
3
|
# Exceptions
|
6
4
|
require 'service_actor/error'
|
7
5
|
require 'service_actor/failure'
|
@@ -19,6 +17,7 @@ require 'service_actor/nil_checkable'
|
|
19
17
|
require 'service_actor/conditionable'
|
20
18
|
require 'service_actor/defaultable'
|
21
19
|
require 'service_actor/collectionable'
|
20
|
+
require 'service_actor/failable'
|
22
21
|
|
23
22
|
module ServiceActor
|
24
23
|
module Base
|
@@ -32,8 +31,9 @@ module ServiceActor
|
|
32
31
|
base.include(TypeCheckable)
|
33
32
|
base.include(NilCheckable)
|
34
33
|
base.include(Conditionable)
|
35
|
-
base.include(Defaultable)
|
36
34
|
base.include(Collectionable)
|
35
|
+
base.include(Defaultable)
|
36
|
+
base.include(Failable)
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
@@ -22,7 +22,8 @@ module ServiceActor
|
|
22
22
|
next if options[:in].include?(result[key])
|
23
23
|
|
24
24
|
raise ArgumentError,
|
25
|
-
"Input #{key} must be included in #{options[:in].inspect}"
|
25
|
+
"Input #{key} must be included in #{options[:in].inspect} " \
|
26
|
+
"but instead was #{result[key].inspect}"
|
26
27
|
end
|
27
28
|
|
28
29
|
super
|
data/lib/service_actor/core.rb
CHANGED
@@ -38,12 +38,15 @@ module ServiceActor
|
|
38
38
|
# To implement in your actors.
|
39
39
|
def rollback; end
|
40
40
|
|
41
|
+
# This method is used internally to override behavior on call. Overriding
|
42
|
+
# `call` instead would mean that end-users have to call `super` in their
|
43
|
+
# actors.
|
41
44
|
# :nodoc:
|
42
45
|
def _call
|
43
46
|
call
|
44
47
|
end
|
45
48
|
|
46
|
-
|
49
|
+
protected
|
47
50
|
|
48
51
|
# Returns the current context from inside an actor.
|
49
52
|
attr_reader :result
|
data/lib/service_actor/error.rb
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ServiceActor
|
4
|
+
# Adds the `fail_on` DSL to actors. This allows you to call `.result` and get
|
5
|
+
# back a failed actor instead of raising an exception.
|
6
|
+
#
|
7
|
+
# class ApplicationActor < Actor
|
8
|
+
# fail_on ServiceActor::ArgumentError
|
9
|
+
# end
|
10
|
+
module Failable
|
11
|
+
def self.included(base)
|
12
|
+
base.extend(ClassMethods)
|
13
|
+
base.prepend(PrependedMethods)
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def inherited(child)
|
18
|
+
super
|
19
|
+
|
20
|
+
child.fail_ons.push(*fail_ons)
|
21
|
+
end
|
22
|
+
|
23
|
+
def fail_on(*exceptions)
|
24
|
+
fail_ons.push(*exceptions)
|
25
|
+
end
|
26
|
+
|
27
|
+
def fail_ons
|
28
|
+
@fail_ons ||= []
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module PrependedMethods
|
33
|
+
def _call
|
34
|
+
super
|
35
|
+
rescue *self.class.fail_ons => e
|
36
|
+
fail!(error: e.message)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -31,20 +31,15 @@ module ServiceActor
|
|
31
31
|
|
32
32
|
raise ArgumentError,
|
33
33
|
"The #{origin} \"#{name}\" on #{self.class} does not allow " \
|
34
|
-
'nil values
|
34
|
+
'nil values'
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
38
|
def allow_nil?(options)
|
39
|
-
if options.key?(:allow_nil)
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
elsif options[:type]
|
44
|
-
false
|
45
|
-
else
|
46
|
-
true
|
47
|
-
end
|
39
|
+
return options[:allow_nil] if options.key?(:allow_nil)
|
40
|
+
return true if options.key?(:default) && options[:default].nil?
|
41
|
+
|
42
|
+
!options[:type]
|
48
43
|
end
|
49
44
|
end
|
50
45
|
end
|
@@ -48,7 +48,7 @@ module ServiceActor
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def rollback
|
51
|
-
return unless @played
|
51
|
+
return unless defined?(@played)
|
52
52
|
|
53
53
|
@played.each do |actor|
|
54
54
|
next unless actor.respond_to?(:rollback)
|
@@ -60,16 +60,27 @@ module ServiceActor
|
|
60
60
|
private
|
61
61
|
|
62
62
|
def play_actor(actor)
|
63
|
-
|
64
|
-
actor
|
65
|
-
actor.
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
63
|
+
play_service_actor(actor) ||
|
64
|
+
play_interactor(actor) ||
|
65
|
+
actor.call(result)
|
66
|
+
end
|
67
|
+
|
68
|
+
def play_service_actor(actor)
|
69
|
+
return unless actor.is_a?(Class)
|
70
|
+
return unless actor.ancestors.include?(ServiceActor::Core)
|
71
|
+
|
72
|
+
actor = actor.new(result)
|
73
|
+
actor._call
|
70
74
|
|
71
75
|
(@played ||= []).unshift(actor)
|
72
76
|
end
|
77
|
+
|
78
|
+
def play_interactor(actor)
|
79
|
+
return unless actor.is_a?(Class)
|
80
|
+
return unless actor.ancestors.map(&:name).include?('Interactor')
|
81
|
+
|
82
|
+
result.merge!(actor.call(result).to_h)
|
83
|
+
end
|
73
84
|
end
|
74
85
|
end
|
75
86
|
end
|
data/lib/service_actor/result.rb
CHANGED
@@ -28,7 +28,7 @@ module ServiceActor
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def failure?
|
31
|
-
|
31
|
+
self[:failure?] || false
|
32
32
|
end
|
33
33
|
|
34
34
|
def merge!(result)
|
@@ -47,7 +47,7 @@ module ServiceActor
|
|
47
47
|
to_h[name]
|
48
48
|
end
|
49
49
|
|
50
|
-
#
|
50
|
+
# Defined here to override the method on `Object`.
|
51
51
|
def display
|
52
52
|
to_h.fetch(:display)
|
53
53
|
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: 3.
|
4
|
+
version: 3.1.3
|
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: 2022-01-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
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'
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: code-scanning-rubocop
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -128,6 +142,7 @@ files:
|
|
128
142
|
- lib/service_actor/core.rb
|
129
143
|
- lib/service_actor/defaultable.rb
|
130
144
|
- lib/service_actor/error.rb
|
145
|
+
- lib/service_actor/failable.rb
|
131
146
|
- lib/service_actor/failure.rb
|
132
147
|
- lib/service_actor/nil_checkable.rb
|
133
148
|
- lib/service_actor/playable.rb
|
@@ -141,6 +156,7 @@ metadata:
|
|
141
156
|
homepage_uri: https://github.com/sunny/actor
|
142
157
|
source_code_uri: https://github.com/sunny/actor
|
143
158
|
changelog_uri: https://github.com/sunny/actor/blob/main/CHANGELOG.md
|
159
|
+
rubygems_mfa_required: 'true'
|
144
160
|
post_install_message:
|
145
161
|
rdoc_options: []
|
146
162
|
require_paths:
|
@@ -149,17 +165,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
149
165
|
requirements:
|
150
166
|
- - ">="
|
151
167
|
- !ruby/object:Gem::Version
|
152
|
-
version: 2.
|
153
|
-
- - "<"
|
154
|
-
- !ruby/object:Gem::Version
|
155
|
-
version: 2.8.0
|
168
|
+
version: '2.4'
|
156
169
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
157
170
|
requirements:
|
158
171
|
- - ">="
|
159
172
|
- !ruby/object:Gem::Version
|
160
173
|
version: '0'
|
161
174
|
requirements: []
|
162
|
-
rubygems_version: 3.1.
|
175
|
+
rubygems_version: 3.1.6
|
163
176
|
signing_key:
|
164
177
|
specification_version: 4
|
165
178
|
summary: Service objects for your application logic
|