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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f91c36f4113eb695e8398ac43395dc85b5f51cb103c2dcdc72b8e351839932a7
4
- data.tar.gz: f536f4d3d552d07544cd46706faca94240711758ba39da68e07db7e819236b24
3
+ metadata.gz: 971f7ed20085d9eb94a796326b5a9f06d9080d765fbea5b3f80e7f311eb48a3e
4
+ data.tar.gz: 38c6168b68139741f7c3209ec8cbceb90028c1ce0029b4a22d5e0535e0c544da
5
5
  SHA512:
6
- metadata.gz: 5e8260d8e84d2ef2c918e01eb13c8ac0417bff3cf3f0683acc3a3d6281184c9ef9d6af1c20d0cba3c65b74ba51a48ae3a72ba40e2c64280df936c307331957b4
7
- data.tar.gz: 0a93c5b2eecc2e1cd7724b3a42ffd7664d4caa069de6066a2ae3f0c40fdc405492c0ec2cea964cf92df6016a34296c07acad48f7f018a6b46bd26a7864be5378
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
- ![Photo of theater seats](https://user-images.githubusercontent.com/132/78340166-e7567000-7595-11ea-97c0-b3e5da2de7a1.png)
7
+ ![Photo of red and black theater seats with lighting from the top](https://user-images.githubusercontent.com/132/78340166-e7567000-7595-11ea-97c0-b3e5da2de7a1.png)
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
- For **Rails generators**, you can use the
44
- [service_actor-rails](https://github.com/sunny/actor-rails) gem:
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 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
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 or lambda.
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: -> context { context.adjective.match?(/^aeiou/) ? "an" : "a" }
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
- return if name.to_sym == :error
8
- return unless ServiceActor::Result.instance_methods.include?(name.to_sym)
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, origin: :input
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, origin: :output
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
- check_classes.each do |check_class|
36
+ CHECK_CLASSES.each do |check_class|
38
37
  argument_errors = check_class.check(
39
38
  check_name: check_name,
40
39
  origin: origin,
@@ -1,12 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class ServiceActor::Checks::Base
4
- class << self
5
- def applicable_to_origin?(_origin)
6
- true
7
- end
8
- end
9
-
10
4
  def initialize
11
5
  @argument_errors = []
12
6
  end
@@ -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(check_name:, input_key:, actor:, conditions:, result:, **)
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?(@value)
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: @input_key,
62
- actor: @actor,
72
+ input_key: input_key,
73
+ actor: actor,
63
74
  inclusion_in: inclusion_in,
64
- value: @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 @inclusion.is_a?(Hash)
72
- @inclusion[:message] ||= DEFAULT_MESSAGE
73
- @inclusion.values_at(:in, :message)
84
+ if inclusion.is_a?(Hash)
85
+ inclusion[:message] ||= DEFAULT_MESSAGE
86
+ inclusion.values_at(:in, :message)
74
87
  else
75
- [@inclusion, DEFAULT_MESSAGE]
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(check_name:, input_key:, actor:, conditions:, result:, **)
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
- @nested_checks.each do |nested_check_name, nested_check_conditions|
60
- message = prepared_message_with(nested_check_name, nested_check_conditions) # rubocop:disable Layout/LineLength
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: @input_key,
67
- actor: @actor,
81
+ input_key: input_key,
82
+ actor: actor,
68
83
  check_name: nested_check_name,
69
- value: @value,
84
+ value: value,
70
85
  )
71
86
  end
72
87
 
73
- @argument_errors
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(@value)
98
+ return if check.call(value)
82
99
 
83
100
  message
84
101
  rescue StandardError => e
85
- "The \"#{@input_key}\" input on \"#{@actor}\" has an error in the code " \
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 @value.nil?
78
+ return unless value.nil?
79
79
 
80
80
  allow_nil, message =
81
- define_allow_nil_and_message_from(@input_options[:allow_nil])
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: @origin,
88
- input_key: @input_key,
89
- actor: @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 @input_options.key?(:default) && @input_options[:default].nil?
109
+ if input_options.key?(:default) && input_options[:default].nil?
108
110
  return true
109
111
  end
110
112
 
111
- !@input_options[:type]
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 @type_definition.nil?
71
- return if @given_type.nil?
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| @given_type.is_a?(type) }
75
+ return if types.any? { |type| type === given_type }
76
76
 
77
77
  add_argument_error(
78
78
  message,
79
- origin: @origin,
80
- input_key: @input_key,
81
- actor: @actor,
82
- expected_type: types.join(", "),
83
- given_type: @given_type.class,
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
- if @type_definition.is_a?(Hash)
91
- @type_definition[:message] ||= DEFAULT_MESSAGE
92
+ definition = type_definition
93
+
94
+ if definition.is_a?(Hash)
95
+ definition[:message] ||= DEFAULT_MESSAGE
92
96
 
93
- @type_definition, message =
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(@type_definition).map do |name|
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
- default = input[:default]
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, options|
56
- unless result.key?(key)
57
- result.send(:"#{key}=", options[:default])
58
- end
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 default_for_normal_mode_with(result, key, default)
67
- result[key] = reify_default(result, default)
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
- unless default
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
- # Raises an error depending on the mode
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
- if actor.is_a?(Symbol)
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)
@@ -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
- # using `filter` instead of `except` to maintain Ruby 2.7 compatibility
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ServiceActor
4
- VERSION = "3.9.4"
4
+ VERSION = "5.0.0"
5
5
  end
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: 3.9.4
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: 2024-09-25 00:00:00.000000000 Z
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: '18.2'
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: '18.2'
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.7'
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.3.26
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: []