service_actor 3.9.4 → 4.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: 86aa1f5411838fa24961e50eff4138d414c86aa99c7bdc5907953f7643406431
4
+ data.tar.gz: 1e0c82c0ba0e3b3c8278fa7d286454f891266972d34144b2ee3aa18a2bddd20d
5
5
  SHA512:
6
- metadata.gz: 5e8260d8e84d2ef2c918e01eb13c8ac0417bff3cf3f0683acc3a3d6281184c9ef9d6af1c20d0cba3c65b74ba51a48ae3a72ba40e2c64280df936c307331957b4
7
- data.tar.gz: 0a93c5b2eecc2e1cd7724b3a42ffd7664d4caa069de6066a2ae3f0c40fdc405492c0ec2cea964cf92df6016a34296c07acad48f7f018a6b46bd26a7864be5378
6
+ metadata.gz: ed8bfce6061647dee123bfdecc2a89f92cb7b4fc09664bef3671d722e59605787acfe3ceb9822b785a4a0ed651b315a9c27a38366516cbeadef9403d92c49709
7
+ data.tar.gz: 9dec2bb8b9d5815c1c8d39108432003575f2f5a9a9431c93c69228c2c13f776eea60794696328e2d9ebc6e13849e2fa3e6db3e6e3ac337c03a39d29b24c578ea
data/README.md CHANGED
@@ -314,20 +314,20 @@ end
314
314
 
315
315
  ### Defaults
316
316
 
317
- Inputs can be optional by providing a `default` value or lambda.
317
+ Inputs can be optional by providing a `default` value in a lambda.
318
318
 
319
319
  ```rb
320
320
  class BuildGreeting < Actor
321
321
  input :name
322
- input :adjective, default: "wonderful"
322
+ input :adjective, default: -> { "wonderful" }
323
323
  input :length_of_time, default: -> { ["day", "week", "month"].sample }
324
324
  input :article,
325
- default: -> context { context.adjective.match?(/^aeiou/) ? "an" : "a" }
325
+ default: -> actor { actor.adjective.match?(/^[aeiou]/) ? "an" : "a" }
326
326
 
327
327
  output :greeting
328
328
 
329
329
  def call
330
- self.greeting = "Have #{article} #{length_of_time}, #{name}!"
330
+ self.greeting = "Have #{article} #{adjective} #{length_of_time}, #{name}!"
331
331
  end
332
332
  end
333
333
 
@@ -338,6 +338,33 @@ actor = BuildGreeting.call(name: "Siobhan", adjective: "elegant")
338
338
  actor.greeting # => "Have an elegant week, Siobhan!"
339
339
  ```
340
340
 
341
+ While lambdas are the preferred way to specify defaults, you can also provide
342
+ a default value without using lambdas by using an immutable object.
343
+
344
+ ```rb
345
+ # frozen_string_literal: true
346
+
347
+ class ExampleActor < Actor
348
+ input :options, default: {
349
+ names: {male: "Iaroslav", female: "Anna"}.freeze,
350
+ country_codes: %w[gb ru].freeze
351
+ }.freeze
352
+ end
353
+ ```
354
+
355
+ Note that default values might be mutated if the values returned by the lambda
356
+ are references to mutable objects, e.g.
357
+
358
+ ```rb
359
+ class ExampleActor < Actor
360
+ input :options, default: -> { Registry::DEFAULT_OPTIONS } # `Registry::DEFAULT_OPTIONS` is not frozen
361
+
362
+ def call
363
+ options[:names] = nil
364
+ end
365
+ end
366
+ ```
367
+
341
368
  ### Allow nil
342
369
 
343
370
  By default inputs accept `nil` values. To raise an error instead:
@@ -471,20 +498,6 @@ end
471
498
  end
472
499
  ```
473
500
 
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
501
  #### Type
489
502
 
490
503
  ```ruby
@@ -16,4 +16,13 @@ module ServiceActor::ArgumentsValidator
16
16
 
17
17
  raise ArgumentError, "Expected #{value} to be a subclass of Exception"
18
18
  end
19
+
20
+ def validate_default_value(value, origin_type:, origin_name:, actor:)
21
+ return if value.is_a?(Proc) || !defined?(Ractor.shareable?) || Ractor.shareable?(value)
22
+
23
+ ::Kernel.warn(
24
+ "DEPRECATED: Actor `#{actor}` has #{origin_type} `#{origin_name}` with default " \
25
+ "which is not a Proc or an immutable object.",
26
+ )
27
+ end
19
28
  end
@@ -26,6 +26,10 @@ module ServiceActor::Attributable
26
26
  name, origin: :input
27
27
  )
28
28
 
29
+ ServiceActor::ArgumentsValidator.validate_default_value(
30
+ arguments[:default], actor: self, origin_type: :input, origin_name: name
31
+ )
32
+
29
33
  inputs[name] = arguments
30
34
 
31
35
  define_method(name) do
@@ -47,6 +51,10 @@ module ServiceActor::Attributable
47
51
  name, origin: :output
48
52
  )
49
53
 
54
+ ServiceActor::ArgumentsValidator.validate_default_value(
55
+ arguments[:default], actor: self, origin_type: :output, origin_name: name
56
+ )
57
+
50
58
  outputs[name] = arguments
51
59
 
52
60
  define_method(name) do
@@ -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
@@ -72,14 +72,14 @@ class ServiceActor::Checks::TypeCheck < ServiceActor::Checks::Base
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
79
  origin: @origin,
80
80
  input_key: @input_key,
81
81
  actor: @actor,
82
- expected_type: types.join(", "),
82
+ expected_type: types.map(&:name).join(", "),
83
83
  given_type: @given_type.class,
84
84
  )
85
85
  end
@@ -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)
@@ -124,9 +124,7 @@ class ServiceActor::Result < BasicObject
124
124
  # Key `_default_output` is an internal datum used by actor class
125
125
  # method `.valuable`. Don't expose it with the rest of the result.
126
126
  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 }
127
+ h.except(:_default_output)
130
128
  end
131
129
 
132
130
  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 = "4.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: 4.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-02-24 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: zeitwerk
@@ -206,6 +205,20 @@ 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'
209
222
  description: Service objects for your application logic
210
223
  email:
211
224
  - sunny@sunfox.org
@@ -247,7 +260,6 @@ metadata:
247
260
  source_code_uri: https://github.com/sunny/actor
248
261
  changelog_uri: https://github.com/sunny/actor/blob/main/CHANGELOG.md
249
262
  rubygems_mfa_required: 'true'
250
- post_install_message:
251
263
  rdoc_options: []
252
264
  require_paths:
253
265
  - lib
@@ -255,15 +267,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
255
267
  requirements:
256
268
  - - ">="
257
269
  - !ruby/object:Gem::Version
258
- version: '2.7'
270
+ version: '3.0'
259
271
  required_rubygems_version: !ruby/object:Gem::Requirement
260
272
  requirements:
261
273
  - - ">="
262
274
  - !ruby/object:Gem::Version
263
275
  version: '0'
264
276
  requirements: []
265
- rubygems_version: 3.3.26
266
- signing_key:
277
+ rubygems_version: 3.6.5
267
278
  specification_version: 4
268
279
  summary: Service objects for your application logic
269
280
  test_files: []