service_actor 3.3.0 → 3.4.1

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.
@@ -1,91 +1,89 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ServiceActor
4
- # Play class method to call a series of actors with the same result. On
5
- # failure, calls rollback on actors that succeeded.
6
- #
7
- # class CreateUser < Actor
8
- # play SaveUser,
9
- # CreateSettings,
10
- # SendWelcomeEmail
11
- # end
12
- module Playable
13
- def self.included(base)
14
- base.extend(ClassMethods)
15
- base.prepend(PrependedMethods)
16
- end
3
+ # Play class method to call a series of actors with the same result. On failure,
4
+ # calls rollback on actors that succeeded.
5
+ #
6
+ # class CreateUser < Actor
7
+ # play SaveUser,
8
+ # CreateSettings,
9
+ # SendWelcomeEmail
10
+ # end
11
+ module ServiceActor::Playable
12
+ def self.included(base)
13
+ base.extend(ClassMethods)
14
+ base.prepend(PrependedMethods)
15
+ end
17
16
 
18
- module ClassMethods
19
- def play(*actors, **options)
20
- play_actors.push(actors: actors, **options)
21
- end
17
+ module ClassMethods
18
+ def play(*actors, **options)
19
+ play_actors.push(actors: actors, **options)
20
+ end
22
21
 
23
- def play_actors
24
- @play_actors ||= []
25
- end
22
+ def play_actors
23
+ @play_actors ||= []
24
+ end
26
25
 
27
- def inherited(child)
28
- super
26
+ def inherited(child)
27
+ super
29
28
 
30
- child.play_actors.concat(play_actors)
31
- end
29
+ child.play_actors.concat(play_actors)
32
30
  end
31
+ end
33
32
 
34
- module PrependedMethods
35
- def call
36
- self.class.play_actors.each do |options|
37
- next if options[:if] && !options[:if].call(result)
33
+ module PrependedMethods
34
+ def call
35
+ self.class.play_actors.each do |options|
36
+ next if options[:if] && !options[:if].call(result)
38
37
 
39
- options[:actors].each { |actor| play_actor(actor) }
40
- end
41
- rescue Failure
42
- rollback
43
- raise
38
+ options[:actors].each { |actor| play_actor(actor) }
44
39
  end
40
+ rescue self.class.failure_class
41
+ rollback
42
+ raise
43
+ end
45
44
 
46
- def rollback
47
- return unless defined?(@played_actors)
45
+ def rollback
46
+ return unless defined?(@played_actors)
48
47
 
49
- @played_actors.each do |actor|
50
- next unless actor.respond_to?(:rollback)
48
+ @played_actors.each do |actor|
49
+ next unless actor.respond_to?(:rollback)
51
50
 
52
- actor.rollback
53
- end
51
+ actor.rollback
54
52
  end
53
+ end
55
54
 
56
- private
55
+ private
57
56
 
58
- def play_actor(actor)
59
- play_service_actor(actor) ||
60
- play_method(actor) ||
61
- play_interactor(actor) ||
62
- actor.call(result)
63
- end
57
+ def play_actor(actor)
58
+ play_service_actor(actor) ||
59
+ play_method(actor) ||
60
+ play_interactor(actor) ||
61
+ actor.call(result)
62
+ end
64
63
 
65
- def play_service_actor(actor)
66
- return unless actor.is_a?(Class)
67
- return unless actor.ancestors.include?(ServiceActor::Core)
64
+ def play_service_actor(actor)
65
+ return unless actor.is_a?(Class)
66
+ return unless actor.ancestors.include?(ServiceActor::Core)
68
67
 
69
- actor = actor.new(result)
70
- actor._call
68
+ actor = actor.new(result)
69
+ actor._call
71
70
 
72
- (@played_actors ||= []).unshift(actor)
73
- end
71
+ (@played_actors ||= []).unshift(actor)
72
+ end
74
73
 
75
- def play_method(actor)
76
- return unless actor.is_a?(Symbol)
74
+ def play_method(actor)
75
+ return unless actor.is_a?(Symbol)
77
76
 
78
- send(actor)
77
+ send(actor)
79
78
 
80
- true
81
- end
79
+ true
80
+ end
82
81
 
83
- def play_interactor(actor)
84
- return unless actor.is_a?(Class)
85
- return unless actor.ancestors.map(&:name).include?("Interactor")
82
+ def play_interactor(actor)
83
+ return unless actor.is_a?(Class)
84
+ return unless actor.ancestors.map(&:name).include?("Interactor")
86
85
 
87
- result.merge!(actor.call(result).to_h)
88
- end
86
+ result.merge!(actor.call(result).to_h)
89
87
  end
90
88
  end
91
89
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ServiceActor::Raisable
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ def inherited(child)
10
+ super
11
+
12
+ child.argument_error_class =
13
+ argument_error_class || ServiceActor::ArgumentError
14
+
15
+ child.failure_class = failure_class || ServiceActor::Failure
16
+ end
17
+
18
+ attr_accessor :argument_error_class, :failure_class
19
+ end
20
+ end
@@ -2,77 +2,75 @@
2
2
 
3
3
  require "ostruct"
4
4
 
5
- module ServiceActor
6
- # Represents the context of an actor, holding the data from both its inputs
7
- # and outputs.
8
- class Result < OpenStruct
9
- def self.to_result(data)
10
- return data if data.is_a?(self)
11
-
12
- new(data.to_h)
13
- end
5
+ # Represents the context of an actor, holding the data from both its inputs
6
+ # and outputs.
7
+ class ServiceActor::Result < OpenStruct
8
+ def self.to_result(data)
9
+ return data if data.is_a?(self)
14
10
 
15
- def inspect
16
- "<#{self.class.name} #{to_h}>"
17
- end
11
+ new(data.to_h)
12
+ end
18
13
 
19
- def fail!(result = {})
20
- merge!(result)
21
- merge!(failure?: true)
14
+ def inspect
15
+ "<#{self.class.name} #{to_h}>"
16
+ end
22
17
 
23
- raise Failure, self
24
- end
18
+ def fail!(failure_class, result = {})
19
+ merge!(result)
20
+ merge!(failure?: true)
25
21
 
26
- def success?
27
- !failure?
28
- end
22
+ raise failure_class, self
23
+ end
29
24
 
30
- def failure?
31
- self[:failure?] || false
32
- end
25
+ def success?
26
+ !failure?
27
+ end
33
28
 
34
- def merge!(result)
35
- result.each_pair do |key, value|
36
- self[key] = value
37
- end
29
+ def failure?
30
+ self[:failure?] || false
31
+ end
38
32
 
39
- self
33
+ def merge!(result)
34
+ result.each_pair do |key, value|
35
+ self[key] = value
40
36
  end
41
37
 
42
- def key?(name)
43
- to_h.key?(name)
44
- end
38
+ self
39
+ end
45
40
 
46
- def [](name)
47
- to_h[name]
48
- end
41
+ def key?(name)
42
+ to_h.key?(name)
43
+ end
49
44
 
50
- # Defined here to override the method on `Object`.
51
- def display
52
- to_h.fetch(:display)
53
- end
45
+ def [](name)
46
+ to_h[name]
47
+ end
54
48
 
55
- private
49
+ # Defined here to override the method on `Object`.
50
+ def display
51
+ to_h.fetch(:display)
52
+ end
56
53
 
57
- def respond_to_missing?(method_name, include_private = false)
58
- method_name.to_s.end_with?("?") || super
59
- end
54
+ private
60
55
 
61
- def method_missing(symbol, *args)
62
- attribute = symbol.to_s.chomp("?")
56
+ def respond_to_missing?(method_name, include_private = false)
57
+ method_name.to_s.end_with?("?") || super
58
+ end
63
59
 
64
- if symbol.to_s.end_with?("?") && respond_to?(attribute)
65
- define_singleton_method symbol do
66
- attribute_value = send(attribute.to_sym)
60
+ def method_missing(symbol, *args)
61
+ attribute = symbol.to_s.chomp("?")
67
62
 
68
- # Same as ActiveSupport’s #present?
69
- attribute_value.respond_to?(:empty?) ? !attribute_value.empty? : !!attribute_value
70
- end
63
+ if symbol.to_s.end_with?("?") && respond_to?(attribute)
64
+ define_singleton_method symbol do
65
+ value = send(attribute.to_sym)
71
66
 
72
- return send(symbol)
67
+ # Same as ActiveSupport’s #present?
68
+ value.respond_to?(:empty?) ? !value.empty? : !!value
73
69
  end
74
70
 
75
- super symbol, *args
71
+ return send(symbol)
76
72
  end
73
+
74
+ super symbol, *args
77
75
  end
78
76
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zeitwerk"
4
+
5
+ module ServiceActor; end
6
+
7
+ lib = File.expand_path("../..", __dir__)
8
+
9
+ loader = Zeitwerk::Loader.new
10
+ loader.tag = "service_actor"
11
+ loader.inflector = Zeitwerk::GemInflector.new(
12
+ File.expand_path("service_actor.rb", lib),
13
+ )
14
+ loader.push_dir(lib)
15
+ loader.ignore(__dir__)
16
+ loader.setup
@@ -1,50 +1,90 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ServiceActor
4
- # Adds `type:` checking to inputs and outputs. Accepts class names or classes
5
- # that should match an ancestor. Also accepts arrays.
6
- #
7
- # Example:
8
- #
9
- # class ReduceOrderAmount < Actor
10
- # input :order, type: "Order"
11
- # input :amount, type: [Integer, Float]
12
- # input :bonus_applied, type: [TrueClass FalseClass]
13
- # end
14
- module TypeCheckable
15
- def self.included(base)
16
- base.prepend(PrependedMethods)
3
+ # Adds `type:` checking to inputs and outputs. Accepts class names or classes
4
+ # that should match an ancestor. Also accepts arrays.
5
+ #
6
+ # Example:
7
+ #
8
+ # class ReduceOrderAmount < Actor
9
+ # input :order, type: "Order"
10
+ # input :amount, type: [Integer, Float]
11
+ # input :bonus_applied, type: [TrueClass, FalseClass]
12
+ # end
13
+ #
14
+ # class ReduceOrderAmount < Actor
15
+ # input :order, type: { is: Order, message: "Order is required" }
16
+ # input :amount, type: { is: Integer, message: "Incorrect amount" }
17
+ #
18
+ # input :bonus_applied,
19
+ # type: {
20
+ # is: [TrueClass, FalseClass],
21
+ # message: (lambda do |origin:, input_key:, actor:, expected_type:, given_type:| # rubocop:disable Layout/LineLength
22
+ # "Wrong type \"#{given_type}\" for \"#{input_key}\". " \
23
+ # "Expected: \"#{expected_type}\""
24
+ # end)
25
+ # }
26
+ # end
27
+ module ServiceActor::TypeCheckable
28
+ def self.included(base)
29
+ base.prepend(PrependedMethods)
30
+ end
31
+
32
+ module PrependedMethods
33
+ DEFAULT_MESSAGE = lambda do
34
+ |origin:, input_key:, actor:, expected_type:, given_type:|
35
+
36
+ "The \"#{input_key}\" #{origin} on \"#{actor}\" must be of type " \
37
+ "\"#{expected_type}\" but was \"#{given_type}\""
17
38
  end
18
39
 
19
- module PrependedMethods
20
- def _call
21
- check_type_definitions(self.class.inputs, kind: "Input")
40
+ private_constant :DEFAULT_MESSAGE
22
41
 
23
- super
42
+ def _call
43
+ check_type_definitions(self.class.inputs, origin: "input")
24
44
 
25
- check_type_definitions(self.class.outputs, kind: "Output")
26
- end
45
+ super
46
+
47
+ check_type_definitions(self.class.outputs, origin: "output")
48
+ end
49
+
50
+ private
27
51
 
28
- private
52
+ def check_type_definitions(definitions, origin:) # rubocop:disable Metrics/MethodLength
53
+ definitions.each do |key, options|
54
+ type_definition = options[:type] || next
55
+ value = result[key] || next
29
56
 
30
- def check_type_definitions(definitions, kind:)
31
- definitions.each do |key, options|
32
- type_definition = options[:type] || next
33
- value = result[key] || next
57
+ types, message = define_types_with(type_definition)
34
58
 
35
- types = types_for_definition(type_definition)
36
- next if types.any? { |type| value.is_a?(type) }
59
+ next if types.any? { |type| value.is_a?(type) }
37
60
 
38
- raise ArgumentError,
39
- "#{kind} #{key} on #{self.class} must be of type " \
40
- "#{types.join(', ')} but was #{value.class}"
41
- end
61
+ raise_error_with(
62
+ message,
63
+ origin: origin,
64
+ input_key: key,
65
+ actor: self.class,
66
+ expected_type: types.join(", "),
67
+ given_type: value.class,
68
+ )
42
69
  end
70
+ end
71
+
72
+ def define_types_with(type_definition)
73
+ if type_definition.is_a?(Hash)
74
+ type_definition, message =
75
+ type_definition.values_at(:is, :message)
76
+ else
77
+ message = DEFAULT_MESSAGE
78
+ end
79
+
80
+ types = types_for_definition(type_definition)
81
+
82
+ [types, message]
83
+ end
43
84
 
44
- def types_for_definition(type_definition)
45
- Array(type_definition).map do |name|
46
- name.is_a?(String) ? Object.const_get(name) : name
47
- end
85
+ def types_for_definition(type_definition)
86
+ Array(type_definition).map do |name|
87
+ name.is_a?(String) ? Object.const_get(name) : name
48
88
  end
49
89
  end
50
90
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ServiceActor
4
- VERSION = "3.3.0"
4
+ VERSION = "3.4.1"
5
5
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: service_actor
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.0
4
+ version: 3.4.1
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-07-18 00:00:00.000000000 Z
11
+ date: 2022-09-24 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: zeitwerk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rspec
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -100,6 +114,20 @@ dependencies:
100
114
  - - ">="
101
115
  - !ruby/object:Gem::Version
102
116
  version: '0'
117
+ - !ruby/object:Gem::Dependency
118
+ name: rubocop-rake
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
103
131
  - !ruby/object:Gem::Dependency
104
132
  name: code-scanning-rubocop
105
133
  requirement: !ruby/object:Gem::Requirement
@@ -152,7 +180,9 @@ files:
152
180
  - lib/service_actor/failure.rb
153
181
  - lib/service_actor/nil_checkable.rb
154
182
  - lib/service_actor/playable.rb
183
+ - lib/service_actor/raisable.rb
155
184
  - lib/service_actor/result.rb
185
+ - lib/service_actor/support/loader.rb
156
186
  - lib/service_actor/type_checkable.rb
157
187
  - lib/service_actor/version.rb
158
188
  homepage: https://github.com/sunny/actor