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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ce3e96190aef06e630c4e930ad942876f27b8f0dc6a00b33c0ff2104d9be9be
4
- data.tar.gz: 7badaaa07f340dfd66924417d214f070fc50346ec3d9805c4d115757b2845e22
3
+ metadata.gz: 40bbf17640409642508a6cb2dc7bd7197a49266ae9639c4a97c5cf52a3696ddd
4
+ data.tar.gz: 6b2627086780fcbead6e4ce3feff3565eab1bc5e3181a743296e8a7099e22480
5
5
  SHA512:
6
- metadata.gz: f186543f09b78804ea899b57bc8c78e174de83fa205d0a36d74b3089a17b1e6e71f2ddc6c285ae673b5e796b69ab76f327879b57ff6d97a95c4473aa62d1f763
7
- data.tar.gz: 7ecedf103e0b66a462a3cfb9f97a7bdb9ec1ea349b3fe7e2824ac716d9a68f68d29f7dd3828099d7ab6569ae90eac573112317c97a3619a7f1072856727fd1c3
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
- - [Result](#result)
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's Gemfile:
36
+ Add these lines to your applications 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 = 'Have a wonderful day!'
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: 'wonderful'
119
- input :length_of_time, default: -> { ['day', 'week', 'month'].sample }
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: 'Jim')
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` (unreleased
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't mess up our
194
+ Sometimes it can help to have a quick way of making sure we didnt 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't match is not an instance of
193
- these types, an error is raised.
198
+ of possible classes. If the input or output doesnt 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
- ### Result
214
+ ### Fail
209
215
 
210
- All actors return a successful result by default. To stop the execution and
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: 'Invalid user') unless user.valid?
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` and call `success?` or `failure?` on the result.
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
- Any keys you add to `fail!` will be added to the result, for example you could
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, you don't have
299
- getters and setters but have access to the shared result:
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 after the
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('Paris') do
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
- ## Use with Rails
346
+ ### Fail on argument error
341
347
 
342
- The [service_actor-rails](https://github.com/sunny/actor-rails/) gem includes a
343
- Rails generator to create an actor and its spec.
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 an "Actor" class, you can build your own by
356
- changing the gem's require statement:
372
+ If you application already uses a class called “Actor”, you can build your own
373
+ by changing the gems require statement:
357
374
 
358
375
  ```rb
359
- gem 'service_actor', require: 'service_actor/base'
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
- However there are a few key differences which make `actor` unique:
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 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).
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
 
@@ -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,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
@@ -29,7 +29,7 @@ module ServiceActor
29
29
  next if check.call(value)
30
30
 
31
31
  raise ArgumentError,
32
- "Input #{key} must #{name} but was #{value.inspect}."
32
+ "Input #{key} must #{name} but was #{value.inspect}"
33
33
  end
34
34
  end
35
35
 
@@ -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
@@ -27,7 +27,7 @@ module ServiceActor
27
27
  next
28
28
  end
29
29
 
30
- raise(ArgumentError, "Input #{name} on #{self.class} is missing.")
30
+ raise(ArgumentError, "Input #{name} on #{self.class} is missing")
31
31
  end
32
32
 
33
33
  super
@@ -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
@@ -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
- 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
@@ -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
- 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
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
@@ -28,7 +28,7 @@ module ServiceActor
28
28
  end
29
29
 
30
30
  def failure?
31
- super || false
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
- # 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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ServiceActor
4
- VERSION = '3.0.0'
4
+ VERSION = '3.1.3'
5
5
  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.0.0
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: 2020-08-20 00:00:00.000000000 Z
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.3.7
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.2
175
+ rubygems_version: 3.1.6
163
176
  signing_key:
164
177
  specification_version: 4
165
178
  summary: Service objects for your application logic