service_actor 3.1.2 → 3.3.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 +111 -84
- data/lib/service_actor/attributable.rb +3 -0
- data/lib/service_actor/base.rb +13 -13
- data/lib/service_actor/collectionable.rb +1 -1
- data/lib/service_actor/conditionable.rb +1 -1
- data/lib/service_actor/core.rb +10 -7
- data/lib/service_actor/defaultable.rb +1 -1
- data/lib/service_actor/error.rb +1 -1
- data/lib/service_actor/nil_checkable.rb +7 -12
- data/lib/service_actor/playable.rb +39 -23
- data/lib/service_actor/result.rb +25 -2
- data/lib/service_actor/type_checkable.rb +3 -3
- data/lib/service_actor/version.rb +1 -1
- data/lib/service_actor.rb +1 -1
- metadata +13 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1cffe28ea4e94682af7f8ee771ad803134e80ce17a9230552d13b4c169b20c7
|
4
|
+
data.tar.gz: 179b2cbf950872b1dbf46d3bd3b24bafbbc3bbb8cd32574f4dbad6a361958abd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1e2804c9d25b20b853300d59976075c5ab451670b7e0b05986507e3e07fe9295efa79d3de34ef2d96a59a2f64154bebd4a88a5726ee308dbaed17c5fac2aa3a5
|
7
|
+
data.tar.gz: 5fc857d43785fe5c6bf5c3723dd91253f4e914f6dccfffc0c258504e306bd4ebc3992ef72a6d37ccc70b4420fba0f86da51165e5fbf0d153b3d64ba9c5bfb18d
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# ServiceActor
|
2
2
|
|
3
3
|

|
4
4
|
|
@@ -18,34 +18,40 @@ 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
|
+
- [Inline actors](#inline-actors)
|
25
25
|
- [Play conditions](#play-conditions)
|
26
26
|
- [Testing](#testing)
|
27
27
|
- [Build your own actor](#build-your-own-actor)
|
28
28
|
- [Influences](#influences)
|
29
29
|
- [Thanks](#thanks)
|
30
|
-
- [Development](#development)
|
31
30
|
- [Contributing](#contributing)
|
32
31
|
- [License](#contributing)
|
33
32
|
|
34
33
|
## Installation
|
35
34
|
|
36
|
-
Add
|
35
|
+
Add the gem to your application’s Gemfile by executing:
|
37
36
|
|
38
|
-
```
|
39
|
-
|
40
|
-
gem 'service_actor'
|
37
|
+
```sh
|
38
|
+
bundle add service_actor
|
41
39
|
```
|
42
40
|
|
43
|
-
|
41
|
+
### Extensions
|
42
|
+
|
43
|
+
For **Rails generators**, you can use the
|
44
44
|
[service_actor-rails](https://github.com/sunny/actor-rails) gem:
|
45
45
|
|
46
|
-
```
|
47
|
-
|
48
|
-
|
46
|
+
```sh
|
47
|
+
bundle add service_actor-rails
|
48
|
+
```
|
49
|
+
|
50
|
+
For **TTY prompts**, you can use the
|
51
|
+
[service_actor-promptable](https://github.com/pboling/service_actor-promptable) gem:
|
52
|
+
|
53
|
+
```sh
|
54
|
+
bundle add service_actor-promptable
|
49
55
|
```
|
50
56
|
|
51
57
|
## Usage
|
@@ -69,9 +75,10 @@ Trigger them in your application with `.call`:
|
|
69
75
|
SendNotification.call # => <ServiceActor::Result…>
|
70
76
|
```
|
71
77
|
|
72
|
-
When called,
|
73
|
-
actors to accept and return multiple arguments. Let
|
74
|
-
and then we
|
78
|
+
When called, an actor returns a result. Reading and writing to this result allows
|
79
|
+
actors to accept and return multiple arguments. Let’s find out how to do that
|
80
|
+
and then we’ll see how to
|
81
|
+
[chain multiple actors togethor](#play-actors-in-a-sequence).
|
75
82
|
|
76
83
|
### Inputs
|
77
84
|
|
@@ -103,7 +110,7 @@ class BuildGreeting < Actor
|
|
103
110
|
output :greeting
|
104
111
|
|
105
112
|
def call
|
106
|
-
self.greeting =
|
113
|
+
self.greeting = "Have a wonderful day!"
|
107
114
|
end
|
108
115
|
end
|
109
116
|
```
|
@@ -111,8 +118,19 @@ end
|
|
111
118
|
The result you get from calling an actor will include the outputs you set:
|
112
119
|
|
113
120
|
```rb
|
114
|
-
|
115
|
-
|
121
|
+
actor = BuildGreeting.call
|
122
|
+
actor.greeting # => "Have a wonderful day!"
|
123
|
+
```
|
124
|
+
|
125
|
+
For every output there is also a boolean method ending with `?` to test its
|
126
|
+
presence:
|
127
|
+
|
128
|
+
```rb
|
129
|
+
if actor.greeting?
|
130
|
+
puts "Greetings is truthy"
|
131
|
+
else
|
132
|
+
puts "Greetings is falsey"
|
133
|
+
end
|
116
134
|
```
|
117
135
|
|
118
136
|
### Defaults
|
@@ -122,8 +140,8 @@ Inputs can be marked as optional by providing a default:
|
|
122
140
|
```rb
|
123
141
|
class BuildGreeting < Actor
|
124
142
|
input :name
|
125
|
-
input :adjective, default:
|
126
|
-
input :length_of_time, default: -> { [
|
143
|
+
input :adjective, default: "wonderful"
|
144
|
+
input :length_of_time, default: -> { ["day", "week", "month"].sample }
|
127
145
|
|
128
146
|
output :greeting
|
129
147
|
|
@@ -136,21 +154,20 @@ end
|
|
136
154
|
This lets you call the actor without specifying those keys:
|
137
155
|
|
138
156
|
```rb
|
139
|
-
|
140
|
-
|
157
|
+
actor = BuildGreeting.call(name: "Jim")
|
158
|
+
actor.greeting # => "Have a wonderful week Jim!"
|
141
159
|
```
|
142
160
|
|
143
161
|
If an input does not have a default, it will raise a error:
|
144
162
|
|
145
163
|
```rb
|
146
|
-
|
164
|
+
BuildGreeting.call
|
147
165
|
=> ServiceActor::ArgumentError: Input name on BuildGreeting is missing.
|
148
166
|
```
|
149
167
|
|
150
168
|
### Conditions
|
151
169
|
|
152
|
-
You can ensure an input is included in a collection by using `in
|
153
|
-
yet):
|
170
|
+
You can ensure an input is included in a collection by using `in`:
|
154
171
|
|
155
172
|
```rb
|
156
173
|
class Pay < Actor
|
@@ -169,7 +186,7 @@ You can also add custom conditions with the name of your choice by using `must`:
|
|
169
186
|
class UpdateAdminUser < Actor
|
170
187
|
input :user,
|
171
188
|
must: {
|
172
|
-
be_an_admin: ->
|
189
|
+
be_an_admin: -> user { user.admin? }
|
173
190
|
}
|
174
191
|
|
175
192
|
# …
|
@@ -192,12 +209,12 @@ end
|
|
192
209
|
|
193
210
|
### Types
|
194
211
|
|
195
|
-
Sometimes it can help to have a quick way of making sure we didn
|
212
|
+
Sometimes it can help to have a quick way of making sure we didn’t mess up our
|
196
213
|
inputs.
|
197
214
|
|
198
215
|
For that you can use the `type` option and giving a class or an array
|
199
|
-
of possible classes. If the input or output doesn
|
200
|
-
|
216
|
+
of possible classes. If the input or output doesn’t match these types, an
|
217
|
+
error is raised.
|
201
218
|
|
202
219
|
```rb
|
203
220
|
class UpdateUser < Actor
|
@@ -208,14 +225,13 @@ class UpdateUser < Actor
|
|
208
225
|
end
|
209
226
|
```
|
210
227
|
|
211
|
-
You may also use strings instead of constants, such as `type:
|
228
|
+
You may also use strings instead of constants, such as `type: "User"`.
|
212
229
|
|
213
230
|
When using a type condition, `allow_nil` defaults to `false`.
|
214
231
|
|
215
|
-
###
|
232
|
+
### Fail
|
216
233
|
|
217
|
-
|
218
|
-
mark an actor as having failed, use `fail!`:
|
234
|
+
To stop the execution and mark an actor as having failed, use `fail!`:
|
219
235
|
|
220
236
|
```rb
|
221
237
|
class UpdateUser
|
@@ -225,7 +241,7 @@ class UpdateUser
|
|
225
241
|
def call
|
226
242
|
user.attributes = attributes
|
227
243
|
|
228
|
-
fail!(error:
|
244
|
+
fail!(error: "Invalid user") unless user.valid?
|
229
245
|
|
230
246
|
# …
|
231
247
|
end
|
@@ -235,7 +251,8 @@ end
|
|
235
251
|
This will raise an error in your app with the given data added to the result.
|
236
252
|
|
237
253
|
To test for the success of your actor instead of raising an exception, use
|
238
|
-
`.result` instead of `.call
|
254
|
+
`.result` instead of `.call`. You can then call `success?` or `failure?` on
|
255
|
+
the result.
|
239
256
|
|
240
257
|
For example in a Rails controller:
|
241
258
|
|
@@ -243,17 +260,17 @@ For example in a Rails controller:
|
|
243
260
|
# app/controllers/users_controller.rb
|
244
261
|
class UsersController < ApplicationController
|
245
262
|
def create
|
246
|
-
|
247
|
-
if
|
248
|
-
redirect_to
|
263
|
+
actor = UpdateUser.result(user: user, attributes: user_attributes)
|
264
|
+
if actor.success?
|
265
|
+
redirect_to actor.user
|
249
266
|
else
|
250
|
-
render :new, notice:
|
267
|
+
render :new, notice: actor.error
|
251
268
|
end
|
252
269
|
end
|
253
270
|
end
|
254
271
|
```
|
255
272
|
|
256
|
-
|
273
|
+
The keys you add to `fail!` will be added to the result, for example you could
|
257
274
|
do: `fail!(error_type: "validation", error_code: "uv52", …)`.
|
258
275
|
|
259
276
|
## Play actors in a sequence
|
@@ -264,7 +281,7 @@ actor can use `play` to call other actors:
|
|
264
281
|
```rb
|
265
282
|
class PlaceOrder < Actor
|
266
283
|
play CreateOrder,
|
267
|
-
|
284
|
+
PayOrder,
|
268
285
|
SendOrderConfirmation,
|
269
286
|
NotifyAdmins
|
270
287
|
end
|
@@ -300,30 +317,55 @@ Rollback is only called on the _previous_ actors in `play` and is not called on
|
|
300
317
|
the failing actor itself. Actors should be kept to a single purpose and not have
|
301
318
|
anything to clean up if they call `fail!`.
|
302
319
|
|
303
|
-
###
|
320
|
+
### Inline actors
|
304
321
|
|
305
|
-
|
306
|
-
|
322
|
+
For small work or preparing the result set for the next actors, you can create
|
323
|
+
inline actors by using lambdas. Each lambda has access to the shared result. For
|
324
|
+
example:
|
307
325
|
|
308
326
|
```rb
|
309
|
-
class
|
310
|
-
|
327
|
+
class PayOrder < Actor
|
328
|
+
input :order
|
329
|
+
|
330
|
+
play -> actor { actor.order.currency ||= "EUR" },
|
311
331
|
CreatePayment,
|
312
|
-
|
313
|
-
|
332
|
+
UpdateOrderBalance,
|
333
|
+
-> actor { Logger.info("Order #{actor.order.id} paid") }
|
314
334
|
end
|
315
335
|
```
|
316
336
|
|
317
|
-
|
318
|
-
result for the next actors. If you want to do more work before, or after the
|
319
|
-
whole `play`, you can also override the `call` method. For example:
|
337
|
+
You can also call instance methods. For example:
|
320
338
|
|
321
339
|
```rb
|
322
|
-
class
|
340
|
+
class PayOrder < Actor
|
341
|
+
input :order
|
342
|
+
|
343
|
+
play :assign_default_currency,
|
344
|
+
CreatePayment,
|
345
|
+
UpdateOrderBalance,
|
346
|
+
:log_payment
|
347
|
+
|
348
|
+
private
|
349
|
+
|
350
|
+
def assign_default_currency
|
351
|
+
order.currency ||= "EUR"
|
352
|
+
end
|
353
|
+
|
354
|
+
def log_payment
|
355
|
+
Logger.info("Order #{order.id} paid")
|
356
|
+
end
|
357
|
+
end
|
358
|
+
```
|
359
|
+
|
360
|
+
If you want to do work around the whole actor, you can also override the `call`
|
361
|
+
method. For example:
|
362
|
+
|
363
|
+
```rb
|
364
|
+
class PayOrder < Actor
|
323
365
|
# …
|
324
366
|
|
325
367
|
def call
|
326
|
-
Time.with_timezone(
|
368
|
+
Time.with_timezone("Paris") do
|
327
369
|
super
|
328
370
|
end
|
329
371
|
end
|
@@ -338,12 +380,10 @@ Actors in a play can be called conditionally:
|
|
338
380
|
class PlaceOrder < Actor
|
339
381
|
play CreateOrder,
|
340
382
|
Pay
|
341
|
-
play NotifyAdmins, if: ->
|
383
|
+
play NotifyAdmins, if: -> actor { actor.order.amount > 42 }
|
342
384
|
end
|
343
385
|
```
|
344
386
|
|
345
|
-
You can use this to trigger an early success.
|
346
|
-
|
347
387
|
### Fail on argument error
|
348
388
|
|
349
389
|
By default, errors on inputs will raise an error, even when using `.result`
|
@@ -354,6 +394,8 @@ catch the exception to treat it as an actor failure:
|
|
354
394
|
class PlaceOrder < Actor
|
355
395
|
fail_on ServiceActor::ArgumentError
|
356
396
|
|
397
|
+
input :currency, in: ["EUR", "USD"]
|
398
|
+
|
357
399
|
# …
|
358
400
|
end
|
359
401
|
```
|
@@ -368,11 +410,11 @@ make it easier for you to test your application.
|
|
368
410
|
|
369
411
|
## Build your own actor
|
370
412
|
|
371
|
-
If you application already uses
|
372
|
-
changing the gem
|
413
|
+
If you application already uses a class called “Actor”, you can build your own
|
414
|
+
by changing the gem’s require statement:
|
373
415
|
|
374
416
|
```rb
|
375
|
-
gem
|
417
|
+
gem "service_actor", require: "service_actor/base"
|
376
418
|
```
|
377
419
|
|
378
420
|
And building your own class to inherit from:
|
@@ -387,11 +429,11 @@ end
|
|
387
429
|
|
388
430
|
This gem is heavily influenced by
|
389
431
|
[Interactor](https://github.com/collectiveidea/interactor) ♥.
|
390
|
-
Some key differences make
|
432
|
+
Some key differences make Actor unique:
|
391
433
|
|
392
434
|
- Does not [hide errors when an actor fails inside another
|
393
435
|
actor](https://github.com/collectiveidea/interactor/issues/170).
|
394
|
-
- Requires you to document
|
436
|
+
- Requires you to document arguments with `input` and `output`.
|
395
437
|
- Defaults to raising errors on failures: actor uses `call` and `result`
|
396
438
|
instead of `call!` and `call`. This way, the _default_ is to raise an error
|
397
439
|
and failures are not hidden away because you forgot to use `!`.
|
@@ -400,7 +442,8 @@ Some key differences make `actor` unique:
|
|
400
442
|
`context.foo = `, `fail!` vs `context.fail!`.
|
401
443
|
- Shorter setup syntax: inherit from `< Actor` vs having to `include Interactor`
|
402
444
|
and `include Interactor::Organizer`.
|
403
|
-
- Organizers allow lambdas, being called multiple times,
|
445
|
+
- Organizers allow lambdas, instance methods, being called multiple times,
|
446
|
+
and having conditions.
|
404
447
|
- Allows early success with conditions inside organizers.
|
405
448
|
- No `before`, `after` and `around` hooks, prefer using `play` with lambdas or
|
406
449
|
overriding `call`.
|
@@ -413,33 +456,17 @@ migration.
|
|
413
456
|
Thank you to @nicoolas25, @AnneSottise & @williampollet for the early thoughts
|
414
457
|
and feedback on this gem.
|
415
458
|
|
416
|
-
|
417
|
-
|
418
|
-
## Development
|
459
|
+
Thank you to the wonderful
|
460
|
+
[contributors](https://github.com/sunny/actor/graphs/contributors).
|
419
461
|
|
420
|
-
|
421
|
-
`bin/rake` to run the tests and linting. You can also run `bin/console` for an
|
422
|
-
interactive prompt.
|
423
|
-
|
424
|
-
To release a new version, update the version number in `version.rb`, and in the
|
425
|
-
`CHANGELOG.md`. Update the `README.md` if there are missing segments, make sure
|
426
|
-
tests and linting are pristine by calling `bundle && bin/rake`, then create a
|
427
|
-
commit for this version.
|
428
|
-
|
429
|
-
You can then run `rake release`, which will assign a git tag, push using git,
|
430
|
-
and push the gem to [rubygems.org](https://rubygems.org).
|
462
|
+
Photo by [Lloyd Dirks](https://unsplash.com/photos/4SLz_RCk6kQ).
|
431
463
|
|
432
464
|
## Contributing
|
433
465
|
|
434
|
-
|
435
|
-
[
|
436
|
-
|
437
|
-
This project is intended to be a safe, welcoming space for collaboration, and
|
438
|
-
everyone interacting in the project’s codebase and issue tracker is expected to
|
439
|
-
adhere to the [Contributor Covenant code of
|
440
|
-
conduct](https://github.com/sunny/actor/blob/main/CODE_OF_CONDUCT.md).
|
466
|
+
See
|
467
|
+
[CONTRIBUTING.md](https://github.com/sunny/actor/blob/main/CONTRIBUTING.md).
|
441
468
|
|
442
469
|
## License
|
443
470
|
|
444
471
|
The gem is available as open source under the terms of the
|
445
|
-
[MIT License](https://
|
472
|
+
[MIT License](https://choosealicense.com/licenses/mit/).
|
data/lib/service_actor/base.rb
CHANGED
@@ -1,23 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Exceptions
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
4
|
+
require "service_actor/error"
|
5
|
+
require "service_actor/failure"
|
6
|
+
require "service_actor/argument_error"
|
7
7
|
|
8
8
|
# Core
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
9
|
+
require "service_actor/core"
|
10
|
+
require "service_actor/attributable"
|
11
|
+
require "service_actor/playable"
|
12
|
+
require "service_actor/result"
|
13
13
|
|
14
14
|
# Concerns
|
15
|
-
require
|
16
|
-
require
|
17
|
-
require
|
18
|
-
require
|
19
|
-
require
|
20
|
-
require
|
15
|
+
require "service_actor/type_checkable"
|
16
|
+
require "service_actor/nil_checkable"
|
17
|
+
require "service_actor/conditionable"
|
18
|
+
require "service_actor/defaultable"
|
19
|
+
require "service_actor/collectionable"
|
20
|
+
require "service_actor/failable"
|
21
21
|
|
22
22
|
module ServiceActor
|
23
23
|
module Base
|
data/lib/service_actor/core.rb
CHANGED
@@ -9,9 +9,9 @@ module ServiceActor
|
|
9
9
|
module ClassMethods
|
10
10
|
# Call an actor with a given result. Returns the result.
|
11
11
|
#
|
12
|
-
# CreateUser.call(name:
|
13
|
-
def call(
|
14
|
-
result = Result.to_result(
|
12
|
+
# CreateUser.call(name: "Joe")
|
13
|
+
def call(result = nil, **arguments)
|
14
|
+
result = Result.to_result(result).merge!(arguments)
|
15
15
|
new(result)._call
|
16
16
|
result
|
17
17
|
end
|
@@ -19,9 +19,9 @@ module ServiceActor
|
|
19
19
|
# Call an actor with arguments. Returns the result and does not raise on
|
20
20
|
# failure.
|
21
21
|
#
|
22
|
-
# CreateUser.result(name:
|
23
|
-
def result(
|
24
|
-
call(
|
22
|
+
# CreateUser.result(name: "Joe")
|
23
|
+
def result(result = nil, **arguments)
|
24
|
+
call(result, **arguments)
|
25
25
|
rescue Failure => e
|
26
26
|
e.result
|
27
27
|
end
|
@@ -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
@@ -16,11 +16,11 @@ module ServiceActor
|
|
16
16
|
|
17
17
|
module PrependedMethods
|
18
18
|
def _call
|
19
|
-
check_context_for_nil(self.class.inputs, origin:
|
19
|
+
check_context_for_nil(self.class.inputs, origin: "input")
|
20
20
|
|
21
21
|
super
|
22
22
|
|
23
|
-
check_context_for_nil(self.class.outputs, origin:
|
23
|
+
check_context_for_nil(self.class.outputs, origin: "output")
|
24
24
|
end
|
25
25
|
|
26
26
|
private
|
@@ -31,20 +31,15 @@ module ServiceActor
|
|
31
31
|
|
32
32
|
raise ArgumentError,
|
33
33
|
"The #{origin} \"#{name}\" on #{self.class} does not allow " \
|
34
|
-
|
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
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module ServiceActor
|
4
4
|
# Play class method to call a series of actors with the same result. On
|
5
|
-
# failure, calls rollback on
|
5
|
+
# failure, calls rollback on actors that succeeded.
|
6
6
|
#
|
7
7
|
# class CreateUser < Actor
|
8
8
|
# play SaveUser,
|
@@ -16,23 +16,19 @@ module ServiceActor
|
|
16
16
|
end
|
17
17
|
|
18
18
|
module ClassMethods
|
19
|
-
def inherited(child)
|
20
|
-
super
|
21
|
-
|
22
|
-
play_actors.each do |actor|
|
23
|
-
child.play_actors << actor
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
19
|
def play(*actors, **options)
|
28
|
-
actors
|
29
|
-
play_actors.push({ actor: actor, **options })
|
30
|
-
end
|
20
|
+
play_actors.push(actors: actors, **options)
|
31
21
|
end
|
32
22
|
|
33
23
|
def play_actors
|
34
24
|
@play_actors ||= []
|
35
25
|
end
|
26
|
+
|
27
|
+
def inherited(child)
|
28
|
+
super
|
29
|
+
|
30
|
+
child.play_actors.concat(play_actors)
|
31
|
+
end
|
36
32
|
end
|
37
33
|
|
38
34
|
module PrependedMethods
|
@@ -40,7 +36,7 @@ module ServiceActor
|
|
40
36
|
self.class.play_actors.each do |options|
|
41
37
|
next if options[:if] && !options[:if].call(result)
|
42
38
|
|
43
|
-
|
39
|
+
options[:actors].each { |actor| play_actor(actor) }
|
44
40
|
end
|
45
41
|
rescue Failure
|
46
42
|
rollback
|
@@ -48,9 +44,9 @@ module ServiceActor
|
|
48
44
|
end
|
49
45
|
|
50
46
|
def rollback
|
51
|
-
return unless @
|
47
|
+
return unless defined?(@played_actors)
|
52
48
|
|
53
|
-
@
|
49
|
+
@played_actors.each do |actor|
|
54
50
|
next unless actor.respond_to?(:rollback)
|
55
51
|
|
56
52
|
actor.rollback
|
@@ -60,15 +56,35 @@ module ServiceActor
|
|
60
56
|
private
|
61
57
|
|
62
58
|
def play_actor(actor)
|
63
|
-
|
64
|
-
actor
|
65
|
-
actor
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
59
|
+
play_service_actor(actor) ||
|
60
|
+
play_method(actor) ||
|
61
|
+
play_interactor(actor) ||
|
62
|
+
actor.call(result)
|
63
|
+
end
|
64
|
+
|
65
|
+
def play_service_actor(actor)
|
66
|
+
return unless actor.is_a?(Class)
|
67
|
+
return unless actor.ancestors.include?(ServiceActor::Core)
|
68
|
+
|
69
|
+
actor = actor.new(result)
|
70
|
+
actor._call
|
71
|
+
|
72
|
+
(@played_actors ||= []).unshift(actor)
|
73
|
+
end
|
74
|
+
|
75
|
+
def play_method(actor)
|
76
|
+
return unless actor.is_a?(Symbol)
|
77
|
+
|
78
|
+
send(actor)
|
79
|
+
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
def play_interactor(actor)
|
84
|
+
return unless actor.is_a?(Class)
|
85
|
+
return unless actor.ancestors.map(&:name).include?("Interactor")
|
70
86
|
|
71
|
-
(
|
87
|
+
result.merge!(actor.call(result).to_h)
|
72
88
|
end
|
73
89
|
end
|
74
90
|
end
|
data/lib/service_actor/result.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "ostruct"
|
4
4
|
|
5
5
|
module ServiceActor
|
6
6
|
# Represents the context of an actor, holding the data from both its inputs
|
@@ -47,9 +47,32 @@ 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
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def respond_to_missing?(method_name, include_private = false)
|
58
|
+
method_name.to_s.end_with?("?") || super
|
59
|
+
end
|
60
|
+
|
61
|
+
def method_missing(symbol, *args)
|
62
|
+
attribute = symbol.to_s.chomp("?")
|
63
|
+
|
64
|
+
if symbol.to_s.end_with?("?") && respond_to?(attribute)
|
65
|
+
define_singleton_method symbol do
|
66
|
+
attribute_value = send(attribute.to_sym)
|
67
|
+
|
68
|
+
# Same as ActiveSupport’s #present?
|
69
|
+
attribute_value.respond_to?(:empty?) ? !attribute_value.empty? : !!attribute_value
|
70
|
+
end
|
71
|
+
|
72
|
+
return send(symbol)
|
73
|
+
end
|
74
|
+
|
75
|
+
super symbol, *args
|
76
|
+
end
|
54
77
|
end
|
55
78
|
end
|
@@ -7,7 +7,7 @@ module ServiceActor
|
|
7
7
|
# Example:
|
8
8
|
#
|
9
9
|
# class ReduceOrderAmount < Actor
|
10
|
-
# input :order, type:
|
10
|
+
# input :order, type: "Order"
|
11
11
|
# input :amount, type: [Integer, Float]
|
12
12
|
# input :bonus_applied, type: [TrueClass FalseClass]
|
13
13
|
# end
|
@@ -18,11 +18,11 @@ module ServiceActor
|
|
18
18
|
|
19
19
|
module PrependedMethods
|
20
20
|
def _call
|
21
|
-
check_type_definitions(self.class.inputs, kind:
|
21
|
+
check_type_definitions(self.class.inputs, kind: "Input")
|
22
22
|
|
23
23
|
super
|
24
24
|
|
25
|
-
check_type_definitions(self.class.outputs, kind:
|
25
|
+
check_type_definitions(self.class.outputs, kind: "Output")
|
26
26
|
end
|
27
27
|
|
28
28
|
private
|
data/lib/service_actor.rb
CHANGED
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.3.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:
|
11
|
+
date: 2022-07-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -53,19 +53,25 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name: rubocop
|
56
|
+
name: rubocop-lts
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '12.0'
|
59
62
|
- - ">="
|
60
63
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
64
|
+
version: 12.0.1
|
62
65
|
type: :development
|
63
66
|
prerelease: false
|
64
67
|
version_requirements: !ruby/object:Gem::Requirement
|
65
68
|
requirements:
|
69
|
+
- - "~>"
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '12.0'
|
66
72
|
- - ">="
|
67
73
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
74
|
+
version: 12.0.1
|
69
75
|
- !ruby/object:Gem::Dependency
|
70
76
|
name: rubocop-rspec
|
71
77
|
requirement: !ruby/object:Gem::Requirement
|
@@ -156,6 +162,7 @@ metadata:
|
|
156
162
|
homepage_uri: https://github.com/sunny/actor
|
157
163
|
source_code_uri: https://github.com/sunny/actor
|
158
164
|
changelog_uri: https://github.com/sunny/actor/blob/main/CHANGELOG.md
|
165
|
+
rubygems_mfa_required: 'true'
|
159
166
|
post_install_message:
|
160
167
|
rdoc_options: []
|
161
168
|
require_paths:
|
@@ -171,7 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
171
178
|
- !ruby/object:Gem::Version
|
172
179
|
version: '0'
|
173
180
|
requirements: []
|
174
|
-
rubygems_version: 3.1.
|
181
|
+
rubygems_version: 3.1.6
|
175
182
|
signing_key:
|
176
183
|
specification_version: 4
|
177
184
|
summary: Service objects for your application logic
|