service_actor 3.3.0 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ lib = File.expand_path("../..", __dir__)
6
+
7
+ loader = Zeitwerk::Loader.new
8
+ loader.tag = "service_actor"
9
+ loader.inflector = Zeitwerk::GemInflector.new(
10
+ File.expand_path("service_actor.rb", lib),
11
+ )
12
+ loader.push_dir(lib)
13
+ loader.ignore(__dir__)
14
+ loader.setup
15
+
16
+ module ServiceActor; end
@@ -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.0"
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.0
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-22 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