service_actor 3.9.4 → 5.0.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 +88 -35
- data/lib/service_actor/arguments_validator.rb +14 -2
- data/lib/service_actor/attributable.rb +18 -2
- data/lib/service_actor/checkable.rb +1 -2
- data/lib/service_actor/checks/base.rb +0 -6
- data/lib/service_actor/checks/inclusion_check.rb +23 -10
- data/lib/service_actor/checks/must_check.rb +27 -10
- data/lib/service_actor/checks/nil_check.rb +9 -7
- data/lib/service_actor/checks/type_check.rb +16 -13
- data/lib/service_actor/defaultable.rb +9 -44
- data/lib/service_actor/playable.rb +9 -1
- data/lib/service_actor/result.rb +2 -3
- data/lib/service_actor/version.rb +1 -1
- metadata +48 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 971f7ed20085d9eb94a796326b5a9f06d9080d765fbea5b3f80e7f311eb48a3e
|
4
|
+
data.tar.gz: 38c6168b68139741f7c3209ec8cbceb90028c1ce0029b4a22d5e0535e0c544da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a26e9cac7270f392e7cb58b96cd6a899f92f36f603ad175e9ec9965449b4a1c0ad58cef053252bc5c31d2dc3b570e3bc77a64a88dc9f5d9890f05268f17b1c2
|
7
|
+
data.tar.gz: bc08967e46472cc15f71f516290f67dd01dd244786ea4881fc9df82db30b2e199171bf88f2a4ad4497d51274cbff24bcd2434de66647f2035cb7d0de785ad28b
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@ This Ruby gem lets you move your application logic into small composable
|
|
4
4
|
service objects. It is a lightweight framework that helps you keep your models
|
5
5
|
and controllers thin.
|
6
6
|
|
7
|
-

|
7
|
+

|
8
8
|
|
9
9
|
## Contents
|
10
10
|
|
@@ -24,6 +24,7 @@ and controllers thin.
|
|
24
24
|
- [Conditions](#conditions)
|
25
25
|
- [Types](#types)
|
26
26
|
- [Custom input errors](#custom-input-errors)
|
27
|
+
- [Custom type validations](#custom-type-validations)
|
27
28
|
- [Testing](#testing)
|
28
29
|
- [FAQ](#faq)
|
29
30
|
- [Thanks](#thanks)
|
@@ -40,19 +41,8 @@ bundle add service_actor
|
|
40
41
|
|
41
42
|
### Extensions
|
42
43
|
|
43
|
-
|
44
|
-
[service_actor-
|
45
|
-
|
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
|
55
|
-
```
|
44
|
+
- Rails generators: [service_actor-rails](https://github.com/sunny/actor-rails)
|
45
|
+
- TTY prompts: [service_actor-promptable](https://github.com/pboling/service_actor-promptable)
|
56
46
|
|
57
47
|
## Usage
|
58
48
|
|
@@ -75,9 +65,9 @@ Trigger them in your application with `.call`:
|
|
75
65
|
SendNotification.call # => <ServiceActor::Result…>
|
76
66
|
```
|
77
67
|
|
78
|
-
When called, an actor returns a result. Reading and writing to this result
|
79
|
-
actors to accept and return multiple arguments. Let’s find out how to do
|
80
|
-
and then we’ll see how to
|
68
|
+
When called, an actor returns a result. Reading and writing to this result
|
69
|
+
allows actors to accept and return multiple arguments. Let’s find out how to do
|
70
|
+
that and then we’ll see how to
|
81
71
|
[chain multiple actors together](#play-actors-in-a-sequence).
|
82
72
|
|
83
73
|
### Inputs
|
@@ -181,6 +171,11 @@ class UsersController < ApplicationController
|
|
181
171
|
end
|
182
172
|
```
|
183
173
|
|
174
|
+
> [!WARNING]
|
175
|
+
> If you specify the type option for output fields, it will not be enforced for
|
176
|
+
> failed actors.
|
177
|
+
> As a result, their output might not match the specified type.
|
178
|
+
|
184
179
|
## Play actors in a sequence
|
185
180
|
|
186
181
|
To help you create actors that are small, single-responsibility actions, an
|
@@ -314,20 +309,20 @@ end
|
|
314
309
|
|
315
310
|
### Defaults
|
316
311
|
|
317
|
-
Inputs can be optional by providing a `default` value
|
312
|
+
Inputs can be optional by providing a `default` value in a lambda.
|
318
313
|
|
319
314
|
```rb
|
320
315
|
class BuildGreeting < Actor
|
321
316
|
input :name
|
322
|
-
input :adjective, default: "wonderful"
|
317
|
+
input :adjective, default: -> { "wonderful" }
|
323
318
|
input :length_of_time, default: -> { ["day", "week", "month"].sample }
|
324
319
|
input :article,
|
325
|
-
default: ->
|
320
|
+
default: -> actor { actor.adjective.match?(/^[aeiou]/) ? "an" : "a" }
|
326
321
|
|
327
322
|
output :greeting
|
328
323
|
|
329
324
|
def call
|
330
|
-
self.greeting = "Have #{article} #{length_of_time}, #{name}!"
|
325
|
+
self.greeting = "Have #{article} #{adjective} #{length_of_time}, #{name}!"
|
331
326
|
end
|
332
327
|
end
|
333
328
|
|
@@ -338,6 +333,34 @@ actor = BuildGreeting.call(name: "Siobhan", adjective: "elegant")
|
|
338
333
|
actor.greeting # => "Have an elegant week, Siobhan!"
|
339
334
|
```
|
340
335
|
|
336
|
+
While lambdas are the preferred way to specify defaults, you can also provide a
|
337
|
+
default value without using lambdas by using an immutable object.
|
338
|
+
|
339
|
+
```rb
|
340
|
+
# frozen_string_literal: true
|
341
|
+
|
342
|
+
class ExampleActor < Actor
|
343
|
+
input :options, default: {
|
344
|
+
names: {man: "Iaroslav", woman: "Anna"}.freeze,
|
345
|
+
country_codes: %w[gb ru].freeze
|
346
|
+
}.freeze
|
347
|
+
end
|
348
|
+
```
|
349
|
+
|
350
|
+
Note that default values might be mutated if the values returned by the lambda
|
351
|
+
are references to mutable objects, e.g.
|
352
|
+
|
353
|
+
```rb
|
354
|
+
class ExampleActor < Actor
|
355
|
+
# `Registry::DEFAULT_OPTIONS` is not frozen
|
356
|
+
input :options, default: -> { Registry::DEFAULT_OPTIONS }
|
357
|
+
|
358
|
+
def call
|
359
|
+
options[:names] = nil
|
360
|
+
end
|
361
|
+
end
|
362
|
+
```
|
363
|
+
|
341
364
|
### Allow nil
|
342
365
|
|
343
366
|
By default inputs accept `nil` values. To raise an error instead:
|
@@ -471,20 +494,6 @@ end
|
|
471
494
|
end
|
472
495
|
```
|
473
496
|
|
474
|
-
#### Default
|
475
|
-
|
476
|
-
```ruby
|
477
|
-
class MultiplyThing < Actor
|
478
|
-
input :multiplier,
|
479
|
-
default: {
|
480
|
-
is: -> { rand(1..10) },
|
481
|
-
message: (lambda do |input_key:, **|
|
482
|
-
"Input \"#{input_key}\" is required"
|
483
|
-
end)
|
484
|
-
}
|
485
|
-
end
|
486
|
-
```
|
487
|
-
|
488
497
|
#### Type
|
489
498
|
|
490
499
|
```ruby
|
@@ -516,6 +525,50 @@ end
|
|
516
525
|
|
517
526
|
</details>
|
518
527
|
|
528
|
+
### Custom type validations
|
529
|
+
|
530
|
+
This gem provides a minimal API for checking the types of `input` and `output`
|
531
|
+
values:
|
532
|
+
|
533
|
+
- A direct class match: `input :age, type: Integer`
|
534
|
+
- A choice between classes: `output :height, type: [Integer, Float]`
|
535
|
+
|
536
|
+
More complex type checks are outside the scope of this gem. However, type
|
537
|
+
checking is performed using Ruby’s `===` method.
|
538
|
+
This means you can define a custom class with a `===` method to implement your
|
539
|
+
own type logic.
|
540
|
+
|
541
|
+
For example, to define a “positive integer” type, you can create a custom class:
|
542
|
+
|
543
|
+
```ruby
|
544
|
+
class PositiveInteger
|
545
|
+
class << self
|
546
|
+
def ===(value)
|
547
|
+
value.is_a?(Integer) && value.positive?
|
548
|
+
end
|
549
|
+
end
|
550
|
+
end
|
551
|
+
```
|
552
|
+
|
553
|
+
Then you can use it in an actor:
|
554
|
+
|
555
|
+
```ruby
|
556
|
+
class AgeActor < Actor
|
557
|
+
input :age, type: PositiveInteger
|
558
|
+
end
|
559
|
+
|
560
|
+
AgeActor.call(age: 25)
|
561
|
+
# => #<ServiceActor::Result {age: 25}>
|
562
|
+
AgeActor.call(age: -42)
|
563
|
+
# ServiceActor::ArgumentError: The "age" input on "AgeActor" must be of type
|
564
|
+
# "PositiveInteger" but was "Integer" (ServiceActor::ArgumentError)
|
565
|
+
```
|
566
|
+
|
567
|
+
This approach also allows you to define adapters for third-party validation
|
568
|
+
gems, providing the flexibility to integrate custom type checks.
|
569
|
+
|
570
|
+
See [more examples](./docs/examples/custom_types).
|
571
|
+
|
519
572
|
## Testing
|
520
573
|
|
521
574
|
In your application, add automated testing to your actors as you would do to any
|
@@ -4,8 +4,11 @@ module ServiceActor::ArgumentsValidator
|
|
4
4
|
module_function
|
5
5
|
|
6
6
|
def validate_origin_name(name, origin:)
|
7
|
-
|
8
|
-
return
|
7
|
+
name = name.to_sym
|
8
|
+
return if name == :error
|
9
|
+
|
10
|
+
methods = ServiceActor::Core.instance_methods + ServiceActor::Result.instance_methods
|
11
|
+
return unless methods.include?(name)
|
9
12
|
|
10
13
|
raise ArgumentError,
|
11
14
|
"#{origin} `#{name}` overrides `ServiceActor::Result` instance method"
|
@@ -16,4 +19,13 @@ module ServiceActor::ArgumentsValidator
|
|
16
19
|
|
17
20
|
raise ArgumentError, "Expected #{value} to be a subclass of Exception"
|
18
21
|
end
|
22
|
+
|
23
|
+
def validate_default_value(value, origin_type:, origin_name:, actor:)
|
24
|
+
return if value.is_a?(Proc) || !defined?(Ractor.shareable?) || Ractor.shareable?(value)
|
25
|
+
|
26
|
+
::Kernel.warn(
|
27
|
+
"DEPRECATED: Actor `#{actor}` has #{origin_type} `#{origin_name}` with default " \
|
28
|
+
"which is not a Proc or an immutable object.",
|
29
|
+
)
|
30
|
+
end
|
19
31
|
end
|
@@ -23,7 +23,15 @@ module ServiceActor::Attributable
|
|
23
23
|
|
24
24
|
def input(name, **arguments)
|
25
25
|
ServiceActor::ArgumentsValidator.validate_origin_name(
|
26
|
-
name,
|
26
|
+
name,
|
27
|
+
origin: :input,
|
28
|
+
)
|
29
|
+
|
30
|
+
ServiceActor::ArgumentsValidator.validate_default_value(
|
31
|
+
arguments[:default],
|
32
|
+
actor: self,
|
33
|
+
origin_type: :input,
|
34
|
+
origin_name: name,
|
27
35
|
)
|
28
36
|
|
29
37
|
inputs[name] = arguments
|
@@ -44,7 +52,15 @@ module ServiceActor::Attributable
|
|
44
52
|
|
45
53
|
def output(name, **arguments)
|
46
54
|
ServiceActor::ArgumentsValidator.validate_origin_name(
|
47
|
-
name,
|
55
|
+
name,
|
56
|
+
origin: :output,
|
57
|
+
)
|
58
|
+
|
59
|
+
ServiceActor::ArgumentsValidator.validate_default_value(
|
60
|
+
arguments[:default],
|
61
|
+
actor: self,
|
62
|
+
origin_type: :output,
|
63
|
+
origin_name: name,
|
48
64
|
)
|
49
65
|
|
50
66
|
outputs[name] = arguments
|
@@ -31,10 +31,9 @@ module ServiceActor::Checkable
|
|
31
31
|
|
32
32
|
# rubocop:disable Metrics/MethodLength
|
33
33
|
def service_actor_checks_for(origin)
|
34
|
-
check_classes = CHECK_CLASSES.select { _1.applicable_to_origin?(origin) }
|
35
34
|
self.class.public_send(:"#{origin}s").each do |input_key, input_options|
|
36
35
|
input_options.each do |check_name, check_conditions|
|
37
|
-
|
36
|
+
CHECK_CLASSES.each do |check_class|
|
38
37
|
argument_errors = check_class.check(
|
39
38
|
check_name: check_name,
|
40
39
|
origin: origin,
|
@@ -28,7 +28,15 @@ class ServiceActor::Checks::InclusionCheck < ServiceActor::Checks::Base
|
|
28
28
|
private_constant :DEFAULT_MESSAGE
|
29
29
|
|
30
30
|
class << self
|
31
|
-
def check(
|
31
|
+
def check(
|
32
|
+
check_name:,
|
33
|
+
input_key:,
|
34
|
+
actor:,
|
35
|
+
conditions:,
|
36
|
+
result:,
|
37
|
+
input_options:,
|
38
|
+
**
|
39
|
+
)
|
32
40
|
# DEPRECATED: `in` is deprecated in favor of `inclusion`.
|
33
41
|
return unless %i[inclusion in].include?(check_name)
|
34
42
|
|
@@ -37,42 +45,47 @@ class ServiceActor::Checks::InclusionCheck < ServiceActor::Checks::Base
|
|
37
45
|
actor: actor,
|
38
46
|
inclusion: conditions,
|
39
47
|
value: result[input_key],
|
48
|
+
input_options: input_options,
|
40
49
|
).check
|
41
50
|
end
|
42
51
|
end
|
43
52
|
|
44
|
-
def initialize(input_key:, actor:, inclusion:, value:)
|
53
|
+
def initialize(input_key:, actor:, inclusion:, value:, input_options:)
|
45
54
|
super()
|
46
55
|
|
47
56
|
@input_key = input_key
|
48
57
|
@actor = actor
|
49
58
|
@inclusion = inclusion
|
50
59
|
@value = value
|
60
|
+
@input_options = input_options
|
51
61
|
end
|
52
62
|
|
53
63
|
def check
|
54
64
|
inclusion_in, message = define_inclusion_and_message
|
55
65
|
|
56
66
|
return if inclusion_in.nil?
|
57
|
-
return if inclusion_in.include?(
|
67
|
+
return if inclusion_in.include?(value)
|
68
|
+
return if input_options[:allow_nil] && value.nil?
|
58
69
|
|
59
70
|
add_argument_error(
|
60
71
|
message,
|
61
|
-
input_key:
|
62
|
-
actor:
|
72
|
+
input_key: input_key,
|
73
|
+
actor: actor,
|
63
74
|
inclusion_in: inclusion_in,
|
64
|
-
value:
|
75
|
+
value: value,
|
65
76
|
)
|
66
77
|
end
|
67
78
|
|
68
79
|
private
|
69
80
|
|
81
|
+
attr_reader :value, :inclusion, :input_key, :actor, :input_options
|
82
|
+
|
70
83
|
def define_inclusion_and_message
|
71
|
-
if
|
72
|
-
|
73
|
-
|
84
|
+
if inclusion.is_a?(Hash)
|
85
|
+
inclusion[:message] ||= DEFAULT_MESSAGE
|
86
|
+
inclusion.values_at(:in, :message)
|
74
87
|
else
|
75
|
-
[
|
88
|
+
[inclusion, DEFAULT_MESSAGE]
|
76
89
|
end
|
77
90
|
end
|
78
91
|
end
|
@@ -34,7 +34,15 @@ class ServiceActor::Checks::MustCheck < ServiceActor::Checks::Base
|
|
34
34
|
private_constant :DEFAULT_MESSAGE
|
35
35
|
|
36
36
|
class << self
|
37
|
-
def check(
|
37
|
+
def check(
|
38
|
+
check_name:,
|
39
|
+
input_key:,
|
40
|
+
actor:,
|
41
|
+
conditions:,
|
42
|
+
result:,
|
43
|
+
input_options:,
|
44
|
+
**
|
45
|
+
)
|
38
46
|
return unless check_name == :must
|
39
47
|
|
40
48
|
new(
|
@@ -42,47 +50,56 @@ class ServiceActor::Checks::MustCheck < ServiceActor::Checks::Base
|
|
42
50
|
actor: actor,
|
43
51
|
nested_checks: conditions,
|
44
52
|
value: result[input_key],
|
53
|
+
input_options: input_options,
|
45
54
|
).check
|
46
55
|
end
|
47
56
|
end
|
48
57
|
|
49
|
-
def initialize(input_key:, actor:, nested_checks:, value:)
|
58
|
+
def initialize(input_key:, actor:, nested_checks:, value:, input_options:)
|
50
59
|
super()
|
51
60
|
|
52
61
|
@input_key = input_key
|
53
62
|
@actor = actor
|
54
63
|
@nested_checks = nested_checks
|
55
64
|
@value = value
|
65
|
+
@input_options = input_options
|
56
66
|
end
|
57
67
|
|
58
68
|
def check
|
59
|
-
|
60
|
-
|
69
|
+
return if input_options[:allow_nil] && value.nil?
|
70
|
+
|
71
|
+
nested_checks.each do |nested_check_name, nested_check_conditions|
|
72
|
+
message = prepared_message_with(
|
73
|
+
nested_check_name,
|
74
|
+
nested_check_conditions,
|
75
|
+
)
|
61
76
|
|
62
77
|
next unless message
|
63
78
|
|
64
79
|
add_argument_error(
|
65
80
|
message,
|
66
|
-
input_key:
|
67
|
-
actor:
|
81
|
+
input_key: input_key,
|
82
|
+
actor: actor,
|
68
83
|
check_name: nested_check_name,
|
69
|
-
value:
|
84
|
+
value: value,
|
70
85
|
)
|
71
86
|
end
|
72
87
|
|
73
|
-
|
88
|
+
argument_errors
|
74
89
|
end
|
75
90
|
|
76
91
|
private
|
77
92
|
|
93
|
+
attr_reader :input_key, :actor, :nested_checks, :value, :input_options
|
94
|
+
|
78
95
|
def prepared_message_with(nested_check_name, nested_check_conditions)
|
79
96
|
check, message = define_check_and_message_from(nested_check_conditions)
|
80
97
|
|
81
|
-
return if check.call(
|
98
|
+
return if check.call(value)
|
82
99
|
|
83
100
|
message
|
84
101
|
rescue StandardError => e
|
85
|
-
"The \"#{
|
102
|
+
"The \"#{input_key}\" input on \"#{actor}\" has an error in the code " \
|
86
103
|
"inside \"#{nested_check_name}\": [#{e.class}] #{e.message}"
|
87
104
|
end
|
88
105
|
|
@@ -75,23 +75,25 @@ class ServiceActor::Checks::NilCheck < ServiceActor::Checks::Base
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def check
|
78
|
-
return unless
|
78
|
+
return unless value.nil?
|
79
79
|
|
80
80
|
allow_nil, message =
|
81
|
-
define_allow_nil_and_message_from(
|
81
|
+
define_allow_nil_and_message_from(input_options[:allow_nil])
|
82
82
|
|
83
83
|
return if allow_nil?(allow_nil)
|
84
84
|
|
85
85
|
add_argument_error(
|
86
86
|
message,
|
87
|
-
origin:
|
88
|
-
input_key:
|
89
|
-
actor:
|
87
|
+
origin: origin,
|
88
|
+
input_key: input_key,
|
89
|
+
actor: actor,
|
90
90
|
)
|
91
91
|
end
|
92
92
|
|
93
93
|
private
|
94
94
|
|
95
|
+
attr_reader :origin, :input_key, :input_options, :actor, :allow_nil, :value
|
96
|
+
|
95
97
|
def define_allow_nil_and_message_from(allow_nil)
|
96
98
|
if allow_nil.is_a?(Hash)
|
97
99
|
allow_nil[:message] ||= DEFAULT_MESSAGE
|
@@ -104,10 +106,10 @@ class ServiceActor::Checks::NilCheck < ServiceActor::Checks::Base
|
|
104
106
|
def allow_nil?(tmp_allow_nil)
|
105
107
|
return tmp_allow_nil unless tmp_allow_nil.nil?
|
106
108
|
|
107
|
-
if
|
109
|
+
if input_options.key?(:default) && input_options[:default].nil?
|
108
110
|
return true
|
109
111
|
end
|
110
112
|
|
111
|
-
|
113
|
+
!input_options[:type]
|
112
114
|
end
|
113
115
|
end
|
@@ -67,36 +67,39 @@ class ServiceActor::Checks::TypeCheck < ServiceActor::Checks::Base
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def check
|
70
|
-
return if
|
71
|
-
return if
|
70
|
+
return if type_definition.nil?
|
71
|
+
return if given_type.nil?
|
72
72
|
|
73
73
|
types, message = define_types_and_message
|
74
74
|
|
75
|
-
return if types.any? { |type|
|
75
|
+
return if types.any? { |type| type === given_type }
|
76
76
|
|
77
77
|
add_argument_error(
|
78
78
|
message,
|
79
|
-
origin:
|
80
|
-
input_key:
|
81
|
-
actor:
|
82
|
-
expected_type: types.join(", "),
|
83
|
-
given_type:
|
79
|
+
origin: origin,
|
80
|
+
input_key: input_key,
|
81
|
+
actor: actor,
|
82
|
+
expected_type: types.map(&:name).join(", "),
|
83
|
+
given_type: given_type.class,
|
84
84
|
)
|
85
85
|
end
|
86
86
|
|
87
87
|
private
|
88
88
|
|
89
|
+
attr_reader :origin, :input_key, :actor, :type_definition, :given_type
|
90
|
+
|
89
91
|
def define_types_and_message
|
90
|
-
|
91
|
-
|
92
|
+
definition = type_definition
|
93
|
+
|
94
|
+
if definition.is_a?(Hash)
|
95
|
+
definition[:message] ||= DEFAULT_MESSAGE
|
92
96
|
|
93
|
-
|
94
|
-
@type_definition.values_at(:is, :message)
|
97
|
+
definition, message = definition.values_at(:is, :message)
|
95
98
|
else
|
96
99
|
message = DEFAULT_MESSAGE
|
97
100
|
end
|
98
101
|
|
99
|
-
types = Array(
|
102
|
+
types = Array(definition).map do |name|
|
100
103
|
name.is_a?(String) ? Object.const_get(name) : name
|
101
104
|
end
|
102
105
|
|
@@ -9,22 +9,6 @@
|
|
9
9
|
# input :counter, default: 1
|
10
10
|
# input :multiplier, default: -> { rand(1..10) }
|
11
11
|
# end
|
12
|
-
#
|
13
|
-
# class MultiplyThing < Actor
|
14
|
-
# input :counter,
|
15
|
-
# default: {
|
16
|
-
# is: 1,
|
17
|
-
# message: "Counter is required"
|
18
|
-
# }
|
19
|
-
#
|
20
|
-
# input :multiplier,
|
21
|
-
# default: {
|
22
|
-
# is: -> { rand(1..10) },
|
23
|
-
# message: (lambda do |input_key:, actor:|
|
24
|
-
# "Input \"#{input_key}\" is required"
|
25
|
-
# end)
|
26
|
-
# }
|
27
|
-
# end
|
28
12
|
module ServiceActor::Defaultable
|
29
13
|
class << self
|
30
14
|
def included(base)
|
@@ -43,19 +27,13 @@ module ServiceActor::Defaultable
|
|
43
27
|
)
|
44
28
|
end
|
45
29
|
|
46
|
-
|
47
|
-
|
48
|
-
if default.is_a?(Hash) && default[:is]
|
49
|
-
default_for_advanced_mode_with(result, key, default)
|
50
|
-
else
|
51
|
-
default_for_normal_mode_with(result, key, default)
|
52
|
-
end
|
30
|
+
apply_default_for_origin(key, input)
|
53
31
|
end
|
54
32
|
|
55
|
-
self.class.outputs.each do |key,
|
56
|
-
|
57
|
-
|
58
|
-
|
33
|
+
self.class.outputs.each do |key, output|
|
34
|
+
next if result.key?(key)
|
35
|
+
|
36
|
+
apply_default_for_origin(key, output)
|
59
37
|
end
|
60
38
|
|
61
39
|
super
|
@@ -63,26 +41,13 @@ module ServiceActor::Defaultable
|
|
63
41
|
|
64
42
|
private
|
65
43
|
|
66
|
-
def
|
67
|
-
|
68
|
-
end
|
69
|
-
|
70
|
-
def default_for_advanced_mode_with(result, key, content)
|
71
|
-
default, message = content.values_at(:is, :message)
|
44
|
+
def apply_default_for_origin(origin_name, origin_options)
|
45
|
+
default = origin_options[:default]
|
72
46
|
|
73
|
-
|
74
|
-
raise_error_with(message, input_key: key, actor: self.class)
|
75
|
-
end
|
76
|
-
|
77
|
-
result[key] = reify_default(result, default)
|
78
|
-
|
79
|
-
message.call(key, self.class)
|
47
|
+
result[origin_name] = reify_default(result, default)
|
80
48
|
end
|
81
49
|
|
82
|
-
|
83
|
-
def raise_error_with(message, **arguments)
|
84
|
-
message = message.call(**arguments) if message.is_a?(Proc)
|
85
|
-
|
50
|
+
def raise_error_with(message)
|
86
51
|
raise self.class.argument_error_class, message
|
87
52
|
end
|
88
53
|
|
@@ -90,7 +90,15 @@ module ServiceActor::Playable
|
|
90
90
|
end
|
91
91
|
|
92
92
|
def play_actor(actor)
|
93
|
-
|
93
|
+
actor_includes_kernel = begin
|
94
|
+
actor.respond_to?(:is_a?)
|
95
|
+
rescue NoMethodError
|
96
|
+
false
|
97
|
+
end
|
98
|
+
|
99
|
+
if !actor_includes_kernel
|
100
|
+
actor.call(result)
|
101
|
+
elsif actor.is_a?(Symbol)
|
94
102
|
send(actor)
|
95
103
|
elsif actor.is_a?(Class) && actor.ancestors.include?(ServiceActor::Core)
|
96
104
|
play_service_actor(actor)
|
data/lib/service_actor/result.rb
CHANGED
@@ -19,6 +19,7 @@ class ServiceActor::Result < BasicObject
|
|
19
19
|
instance_variables
|
20
20
|
is_a?
|
21
21
|
kind_of?
|
22
|
+
method
|
22
23
|
methods
|
23
24
|
nil?
|
24
25
|
object_id
|
@@ -124,9 +125,7 @@ class ServiceActor::Result < BasicObject
|
|
124
125
|
# Key `_default_output` is an internal datum used by actor class
|
125
126
|
# method `.valuable`. Don't expose it with the rest of the result.
|
126
127
|
def filter_default_output(h)
|
127
|
-
|
128
|
-
# update once support for 2.7 is dropped
|
129
|
-
h.filter { |k| k != :_default_output }
|
128
|
+
h.except(:_default_output)
|
130
129
|
end
|
131
130
|
|
132
131
|
def respond_to_missing?(method_name, _include_private = false)
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: service_actor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sunny Ripert
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-09-28 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: zeitwerk
|
@@ -72,14 +71,14 @@ dependencies:
|
|
72
71
|
requirements:
|
73
72
|
- - "~>"
|
74
73
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
74
|
+
version: '24.0'
|
76
75
|
type: :development
|
77
76
|
prerelease: false
|
78
77
|
version_requirements: !ruby/object:Gem::Requirement
|
79
78
|
requirements:
|
80
79
|
- - "~>"
|
81
80
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
81
|
+
version: '24.0'
|
83
82
|
- !ruby/object:Gem::Dependency
|
84
83
|
name: standard-rubocop-lts
|
85
84
|
requirement: !ruby/object:Gem::Requirement
|
@@ -206,6 +205,48 @@ dependencies:
|
|
206
205
|
- - ">="
|
207
206
|
- !ruby/object:Gem::Version
|
208
207
|
version: '0.0'
|
208
|
+
- !ruby/object:Gem::Dependency
|
209
|
+
name: ostruct
|
210
|
+
requirement: !ruby/object:Gem::Requirement
|
211
|
+
requirements:
|
212
|
+
- - ">="
|
213
|
+
- !ruby/object:Gem::Version
|
214
|
+
version: '0.0'
|
215
|
+
type: :development
|
216
|
+
prerelease: false
|
217
|
+
version_requirements: !ruby/object:Gem::Requirement
|
218
|
+
requirements:
|
219
|
+
- - ">="
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: '0.0'
|
222
|
+
- !ruby/object:Gem::Dependency
|
223
|
+
name: irb
|
224
|
+
requirement: !ruby/object:Gem::Requirement
|
225
|
+
requirements:
|
226
|
+
- - ">="
|
227
|
+
- !ruby/object:Gem::Version
|
228
|
+
version: '1.14'
|
229
|
+
type: :development
|
230
|
+
prerelease: false
|
231
|
+
version_requirements: !ruby/object:Gem::Requirement
|
232
|
+
requirements:
|
233
|
+
- - ">="
|
234
|
+
- !ruby/object:Gem::Version
|
235
|
+
version: '1.14'
|
236
|
+
- !ruby/object:Gem::Dependency
|
237
|
+
name: rdoc
|
238
|
+
requirement: !ruby/object:Gem::Requirement
|
239
|
+
requirements:
|
240
|
+
- - ">="
|
241
|
+
- !ruby/object:Gem::Version
|
242
|
+
version: '6.10'
|
243
|
+
type: :development
|
244
|
+
prerelease: false
|
245
|
+
version_requirements: !ruby/object:Gem::Requirement
|
246
|
+
requirements:
|
247
|
+
- - ">="
|
248
|
+
- !ruby/object:Gem::Version
|
249
|
+
version: '6.10'
|
209
250
|
description: Service objects for your application logic
|
210
251
|
email:
|
211
252
|
- sunny@sunfox.org
|
@@ -247,7 +288,6 @@ metadata:
|
|
247
288
|
source_code_uri: https://github.com/sunny/actor
|
248
289
|
changelog_uri: https://github.com/sunny/actor/blob/main/CHANGELOG.md
|
249
290
|
rubygems_mfa_required: 'true'
|
250
|
-
post_install_message:
|
251
291
|
rdoc_options: []
|
252
292
|
require_paths:
|
253
293
|
- lib
|
@@ -255,15 +295,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
255
295
|
requirements:
|
256
296
|
- - ">="
|
257
297
|
- !ruby/object:Gem::Version
|
258
|
-
version: '2
|
298
|
+
version: '3.2'
|
259
299
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
260
300
|
requirements:
|
261
301
|
- - ">="
|
262
302
|
- !ruby/object:Gem::Version
|
263
303
|
version: '0'
|
264
304
|
requirements: []
|
265
|
-
rubygems_version: 3.
|
266
|
-
signing_key:
|
305
|
+
rubygems_version: 3.6.5
|
267
306
|
specification_version: 4
|
268
307
|
summary: Service objects for your application logic
|
269
308
|
test_files: []
|