service_actor 3.1.2 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 182079e8ce5159c685b472dcecec697cd640e2006266f9f8e71b1fe829e24743
4
- data.tar.gz: 28522d5045e623f0fb55ac77519720e3d76168f070c108a1785c46b322a04a9f
3
+ metadata.gz: b1cffe28ea4e94682af7f8ee771ad803134e80ce17a9230552d13b4c169b20c7
4
+ data.tar.gz: 179b2cbf950872b1dbf46d3bd3b24bafbbc3bbb8cd32574f4dbad6a361958abd
5
5
  SHA512:
6
- metadata.gz: 6c4ae5e139ac02cc1244c24c8b245161cebe2246112bdfe25646c38d09f5b83d34d68f902ded72477149c1f6a928a617d1bbfbf5dd7479ee35d0d6f87c0b08a8
7
- data.tar.gz: 2f0d5c297e68ba61db4467d6ef83e14ec92e12b839a5bea78d0fdd072ae08f7077676fd88cd8ad22f7e1e5790e987be69695c8bb0c465eea58d0e3a59cac7c04
6
+ metadata.gz: 1e2804c9d25b20b853300d59976075c5ab451670b7e0b05986507e3e07fe9295efa79d3de34ef2d96a59a2f64154bebd4a88a5726ee308dbaed17c5fac2aa3a5
7
+ data.tar.gz: 5fc857d43785fe5c6bf5c3723dd91253f4e914f6dccfffc0c258504e306bd4ebc3992ef72a6d37ccc70b4420fba0f86da51165e5fbf0d153b3d64ba9c5bfb18d
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Actor
1
+ # ServiceActor
2
2
 
3
3
  ![Tests](https://github.com/sunny/actor/workflows/Tests/badge.svg)
4
4
 
@@ -18,34 +18,40 @@ and controllers thin.
18
18
  - [Conditions](#conditions)
19
19
  - [Allow nil](#allow-nil)
20
20
  - [Types](#types)
21
- - [Result](#result)
21
+ - [Fail](#fail)
22
22
  - [Play actors in a sequence](#play-actors-in-a-sequence)
23
23
  - [Rollback](#rollback)
24
- - [Lambdas](#lambdas)
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 these lines to your application’s Gemfile:
35
+ Add the gem to your application’s Gemfile by executing:
37
36
 
38
- ```rb
39
- # Composable service objects
40
- gem 'service_actor'
37
+ ```sh
38
+ bundle add service_actor
41
39
  ```
42
40
 
43
- When using Rails, you can include the
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
- ```ruby
47
- # Composable service objects
48
- gem "service_actor-rails"
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, actors return a Result. Reading and writing to this result allows
73
- actors to accept and return multiple arguments. Let's find out how to do that
74
- and then we'll see how to chain multiple actors togethor.
78
+ When called, an actor returns a result. Reading and writing to this result allows
79
+ actors to accept and return multiple arguments. Lets find out how to do that
80
+ and then well 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 = 'Have a wonderful day!'
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
- result = BuildGreeting.call
115
- result.greeting # => "Have a wonderful day!"
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: 'wonderful'
126
- input :length_of_time, default: -> { ['day', 'week', 'month'].sample }
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
- result = BuildGreeting.call(name: 'Jim')
140
- result.greeting # => "Have a wonderful week Jim!"
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
- result = BuildGreeting.call
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` (unreleased
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: ->(user) { user.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't mess up our
212
+ Sometimes it can help to have a quick way of making sure we didnt 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't match is not an instance of
200
- these types, an error is raised.
216
+ of possible classes. If the input or output doesnt 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: 'User'`.
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
- ### Result
232
+ ### Fail
216
233
 
217
- All actors return a successful result by default. To stop the execution and
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: 'Invalid user') unless user.valid?
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` and call `success?` or `failure?` on the result.
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
- result = UpdateUser.result(user: user, attributes: user_attributes)
247
- if result.success?
248
- redirect_to result.user
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: result.error
267
+ render :new, notice: actor.error
251
268
  end
252
269
  end
253
270
  end
254
271
  ```
255
272
 
256
- Any keys you add to `fail!` will be added to the result, for example you could
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
- Pay,
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
- ### Lambdas
320
+ ### Inline actors
304
321
 
305
- You can use inline actions using lambdas. Inside these lambdas, you don't have
306
- getters and setters but have access to the shared result:
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 Pay < Actor
310
- play ->(result) { result.payment_provider = "stripe" },
327
+ class PayOrder < Actor
328
+ input :order
329
+
330
+ play -> actor { actor.order.currency ||= "EUR" },
311
331
  CreatePayment,
312
- ->(result) { result.user_to_notify = result.payment.user },
313
- SendNotification
332
+ UpdateOrderBalance,
333
+ -> actor { Logger.info("Order #{actor.order.id} paid") }
314
334
  end
315
335
  ```
316
336
 
317
- Like in this example, lambdas can be useful for small work or preparing the
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 Pay < Actor
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('Paris') do
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: ->(result) { result.order.amount > 42 }
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 an "Actor" class, you can build your own by
372
- changing the gem's require statement:
413
+ If you application already uses a class called “Actor”, you can build your own
414
+ by changing the gems require statement:
373
415
 
374
416
  ```rb
375
- gem 'service_actor', require: 'service_actor/base'
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 `actor` unique:
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 all arguments with `input` and `output`.
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, and having conditions.
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
- Photo by [Lloyd Dirks](https://unsplash.com/photos/4SLz_RCk6kQ).
417
-
418
- ## Development
459
+ Thank you to the wonderful
460
+ [contributors](https://github.com/sunny/actor/graphs/contributors).
419
461
 
420
- After checking out the repo, run `bin/setup` to install dependencies. Then, run
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
- Bug reports and pull requests are welcome
435
- [on GitHub](https://github.com/sunny/actor).
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://opensource.org/licenses/MIT).
472
+ [MIT License](https://choosealicense.com/licenses/mit/).
@@ -27,6 +27,9 @@ module ServiceActor
27
27
  result[name]
28
28
  end
29
29
 
30
+ # For avoid method redefined warning messages.
31
+ alias_method name, name if method_defined?(name)
32
+
30
33
  protected name
31
34
  end
32
35
 
@@ -1,23 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Exceptions
4
- require 'service_actor/error'
5
- require 'service_actor/failure'
6
- require 'service_actor/argument_error'
4
+ require "service_actor/error"
5
+ require "service_actor/failure"
6
+ require "service_actor/argument_error"
7
7
 
8
8
  # Core
9
- require 'service_actor/core'
10
- require 'service_actor/attributable'
11
- require 'service_actor/playable'
12
- require 'service_actor/result'
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 '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'
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
@@ -7,7 +7,7 @@ module ServiceActor
7
7
  # Example:
8
8
  #
9
9
  # class Pay < Actor
10
- # input :provider, in: ['MANGOPAY', 'PayPal', 'Stripe']
10
+ # input :provider, in: ["MANGOPAY", "PayPal", "Stripe"]
11
11
  # end
12
12
  module Collectionable
13
13
  def self.included(base)
@@ -11,7 +11,7 @@ module ServiceActor
11
11
  # class Pay < Actor
12
12
  # input :provider,
13
13
  # must: {
14
- # exist: ->(provider) { PROVIDERS.include?(provider) }
14
+ # exist: -> provider { PROVIDERS.include?(provider) },
15
15
  # }
16
16
  # end
17
17
  module Conditionable
@@ -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: 'Joe')
13
- def call(options = nil, **arguments)
14
- result = Result.to_result(options).merge!(arguments)
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: 'Joe')
23
- def result(data = nil, **arguments)
24
- call(data, **arguments)
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
- private
49
+ protected
47
50
 
48
51
  # Returns the current context from inside an actor.
49
52
  attr_reader :result
@@ -22,7 +22,7 @@ module ServiceActor
22
22
 
23
23
  if input.key?(:default)
24
24
  default = input[:default]
25
- default = default.call if default.respond_to?(:call)
25
+ default = default.call if default.is_a?(Proc)
26
26
  result[name] = default
27
27
  next
28
28
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ServiceActor
4
- # Standard exception from which other inherit.
4
+ # Standard exception from which others inherit.
5
5
  class Error < StandardError; end
6
6
  end
@@ -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: 'input')
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: 'output')
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
- '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
- options[:allow_nil]
41
- elsif options.key?(:default) && options[:default].nil?
42
- true
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 any actor that succeeded.
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.each do |actor|
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
- play_actor(options[:actor])
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 @played
47
+ return unless defined?(@played_actors)
52
48
 
53
- @played.each do |actor|
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
- if actor.is_a?(Class) && actor.ancestors.include?(Actor)
64
- actor = actor.new(result)
65
- actor._call
66
- else
67
- new_result = actor.call(result)
68
- result.merge!(new_result.to_h) if new_result.respond_to?(:to_h)
69
- end
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
- (@played ||= []).unshift(actor)
87
+ result.merge!(actor.call(result).to_h)
72
88
  end
73
89
  end
74
90
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ostruct'
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
- # Redefined here to override the method on `Object`.
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: 'Order'
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: 'Input')
21
+ check_type_definitions(self.class.inputs, kind: "Input")
22
22
 
23
23
  super
24
24
 
25
- check_type_definitions(self.class.outputs, kind: 'Output')
25
+ check_type_definitions(self.class.outputs, kind: "Output")
26
26
  end
27
27
 
28
28
  private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ServiceActor
4
- VERSION = '3.1.2'
4
+ VERSION = "3.3.0"
5
5
  end
data/lib/service_actor.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'service_actor/base'
3
+ require "service_actor/base"
4
4
 
5
5
  # Class to inherit from in your application.
6
6
  class Actor
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.1.2
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: 2021-09-28 00:00:00.000000000 Z
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: '0'
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: '0'
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.4
181
+ rubygems_version: 3.1.6
175
182
  signing_key:
176
183
  specification_version: 4
177
184
  summary: Service objects for your application logic