service_actor 3.1.3 → 3.2.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: 40bbf17640409642508a6cb2dc7bd7197a49266ae9639c4a97c5cf52a3696ddd
4
- data.tar.gz: 6b2627086780fcbead6e4ce3feff3565eab1bc5e3181a743296e8a7099e22480
3
+ metadata.gz: 8bde0ceb526951927f7349b5bd80dc2d6ca036e40e7dbc3ca98bc027f47fbd4c
4
+ data.tar.gz: cd90e8762fe61eccd643fcdb318ebb27b79a561dbc8075ae4e2c802734ace9f7
5
5
  SHA512:
6
- metadata.gz: 0af13a24439bbab76ce2ae6ddf57e41b86b34c4f81e7d333650675195ab27d2519385e479bc1c41eee3c61f54253812c7e6579fea47bd7d8d17839d76a9808af
7
- data.tar.gz: d25a43b1637a9dd1d79c6c0686ccc9bc2bb69dfbf0667c5a8775b1ac3fffab4c3d2233a955a17d676a0ee17b1af1a583449ac39dea220bbc02df3f82e9df6275
6
+ metadata.gz: 2cddbccf7794088dc4a25b011cdb1155d0b6e855abd4a83630402ec90d4fe2f6c4c7fa4a0935ddca7faee58e49b2ff3d1f9f99b430c76dcc2990452d06d16ded
7
+ data.tar.gz: 81444f049effd685780bd4904c3513c7d7adf220c2ecf4ca6a3bf18e077d1b207786f6b85eeb02597e23700bf2a661c3a83f2c4602333bf85c6adf7e810652ef
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
 
@@ -21,31 +21,37 @@ and controllers thin.
21
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
 
@@ -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
@@ -136,14 +154,14 @@ 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
 
@@ -168,7 +186,7 @@ You can also add custom conditions with the name of your choice by using `must`:
168
186
  class UpdateAdminUser < Actor
169
187
  input :user,
170
188
  must: {
171
- be_an_admin: ->(user) { user.admin? }
189
+ be_an_admin: -> user { user.admin? }
172
190
  }
173
191
 
174
192
  # …
@@ -207,7 +225,7 @@ class UpdateUser < Actor
207
225
  end
208
226
  ```
209
227
 
210
- 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"`.
211
229
 
212
230
  When using a type condition, `allow_nil` defaults to `false`.
213
231
 
@@ -242,11 +260,11 @@ For example in a Rails controller:
242
260
  # app/controllers/users_controller.rb
243
261
  class UsersController < ApplicationController
244
262
  def create
245
- result = UpdateUser.result(user: user, attributes: user_attributes)
246
- if result.success?
247
- redirect_to result.user
263
+ actor = UpdateUser.result(user: user, attributes: user_attributes)
264
+ if actor.success?
265
+ redirect_to actor.user
248
266
  else
249
- render :new, notice: result.error
267
+ render :new, notice: actor.error
250
268
  end
251
269
  end
252
270
  end
@@ -263,7 +281,7 @@ actor can use `play` to call other actors:
263
281
  ```rb
264
282
  class PlaceOrder < Actor
265
283
  play CreateOrder,
266
- Pay,
284
+ PayOrder,
267
285
  SendOrderConfirmation,
268
286
  NotifyAdmins
269
287
  end
@@ -299,26 +317,51 @@ Rollback is only called on the _previous_ actors in `play` and is not called on
299
317
  the failing actor itself. Actors should be kept to a single purpose and not have
300
318
  anything to clean up if they call `fail!`.
301
319
 
302
- ### Lambdas
320
+ ### Inline actors
303
321
 
304
- You can use inline actions using lambdas. Inside these lambdas you have access to
305
- 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:
306
325
 
307
326
  ```rb
308
- class Pay < Actor
309
- play ->(result) { result.payment_provider = "stripe" },
327
+ class PayOrder < Actor
328
+ input :order
329
+
330
+ play -> actor { actor.order.currency ||= "EUR" },
310
331
  CreatePayment,
311
- ->(result) { result.user_to_notify = result.payment.user },
312
- SendNotification
332
+ UpdateOrderBalance,
333
+ -> actor { Logger.info("Order #{actor.order.id} paid") }
313
334
  end
314
335
  ```
315
336
 
316
- Like in this example, lambdas can be useful for small work or preparing the
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:
337
+ You can also call instance methods. For example:
319
338
 
320
339
  ```rb
321
- 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
322
365
  # …
323
366
 
324
367
  def call
@@ -337,12 +380,10 @@ Actors in a play can be called conditionally:
337
380
  class PlaceOrder < Actor
338
381
  play CreateOrder,
339
382
  Pay
340
- play NotifyAdmins, if: ->(result) { result.order.amount > 42 }
383
+ play NotifyAdmins, if: -> actor { actor.order.amount > 42 }
341
384
  end
342
385
  ```
343
386
 
344
- You can use this to trigger an early success.
345
-
346
387
  ### Fail on argument error
347
388
 
348
389
  By default, errors on inputs will raise an error, even when using `.result`
@@ -392,7 +433,7 @@ Some key differences make Actor unique:
392
433
 
393
434
  - Does not [hide errors when an actor fails inside another
394
435
  actor](https://github.com/collectiveidea/interactor/issues/170).
395
- - Requires you to document all arguments with `input` and `output`.
436
+ - Requires you to document arguments with `input` and `output`.
396
437
  - Defaults to raising errors on failures: actor uses `call` and `result`
397
438
  instead of `call!` and `call`. This way, the _default_ is to raise an error
398
439
  and failures are not hidden away because you forgot to use `!`.
@@ -401,7 +442,8 @@ Some key differences make Actor unique:
401
442
  `context.foo = `, `fail!` vs `context.fail!`.
402
443
  - Shorter setup syntax: inherit from `< Actor` vs having to `include Interactor`
403
444
  and `include Interactor::Organizer`.
404
- - Organizers allow lambdas, being called multiple times, and having conditions.
445
+ - Organizers allow lambdas, instance methods, being called multiple times,
446
+ and having conditions.
405
447
  - Allows early success with conditions inside organizers.
406
448
  - No `before`, `after` and `around` hooks, prefer using `play` with lambdas or
407
449
  overriding `call`.
@@ -414,33 +456,17 @@ migration.
414
456
  Thank you to @nicoolas25, @AnneSottise & @williampollet for the early thoughts
415
457
  and feedback on this gem.
416
458
 
417
- Photo by [Lloyd Dirks](https://unsplash.com/photos/4SLz_RCk6kQ).
418
-
419
- ## Development
459
+ Thank you to the wonderful
460
+ [contributors](https://github.com/sunny/actor/graphs/contributors).
420
461
 
421
- After checking out the repo, run `bin/setup` to install dependencies. Then, run
422
- `bin/rake` to run the tests and linting. You can also run `bin/console` for an
423
- interactive prompt.
424
-
425
- To release a new version, update the version number in `version.rb`, and in the
426
- `CHANGELOG.md`. Update the `README.md` if there are missing segments, make sure
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).
462
+ Photo by [Lloyd Dirks](https://unsplash.com/photos/4SLz_RCk6kQ).
432
463
 
433
464
  ## Contributing
434
465
 
435
- Bug reports and pull requests are welcome
436
- [on GitHub](https://github.com/sunny/actor).
437
-
438
- This project is intended to be a safe, welcoming space for collaboration, and
439
- everyone interacting in the project’s codebase and issue tracker is expected to
440
- adhere to the [Contributor Covenant code of
441
- 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).
442
468
 
443
469
  ## License
444
470
 
445
471
  The gem is available as open source under the terms of the
446
- [MIT License](https://opensource.org/licenses/MIT).
472
+ [MIT License](https://choosealicense.com/licenses/mit/).
@@ -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
@@ -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,7 +31,7 @@ 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
 
@@ -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,14 +16,6 @@ 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
20
  actors.each do |actor|
29
21
  play_actors.push({ actor: actor, **options })
@@ -33,6 +25,14 @@ module ServiceActor
33
25
  def play_actors
34
26
  @play_actors ||= []
35
27
  end
28
+
29
+ def inherited(child)
30
+ super
31
+
32
+ play_actors.each do |actor|
33
+ child.play_actors << actor
34
+ end
35
+ end
36
36
  end
37
37
 
38
38
  module PrependedMethods
@@ -61,6 +61,7 @@ module ServiceActor
61
61
 
62
62
  def play_actor(actor)
63
63
  play_service_actor(actor) ||
64
+ play_symbol(actor) ||
64
65
  play_interactor(actor) ||
65
66
  actor.call(result)
66
67
  end
@@ -75,9 +76,17 @@ module ServiceActor
75
76
  (@played ||= []).unshift(actor)
76
77
  end
77
78
 
79
+ def play_symbol(actor)
80
+ return unless actor.is_a?(Symbol)
81
+
82
+ send(actor)
83
+
84
+ true
85
+ end
86
+
78
87
  def play_interactor(actor)
79
88
  return unless actor.is_a?(Class)
80
- return unless actor.ancestors.map(&:name).include?('Interactor')
89
+ return unless actor.ancestors.map(&:name).include?("Interactor")
81
90
 
82
91
  result.merge!(actor.call(result).to_h)
83
92
  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
@@ -51,5 +51,28 @@ module ServiceActor
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.3'
4
+ VERSION = "3.2.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.3
4
+ version: 3.2.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: 2022-01-21 00:00:00.000000000 Z
11
+ date: 2022-07-11 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