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,39 +1,74 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ServiceActor
4
- # Add checks to your inputs, by calling lambdas with the name of you choice
5
- # under the "must" key.
6
- #
7
- # Will raise an error if any check returns a truthy value.
8
- #
9
- # Example:
10
- #
11
- # class Pay < Actor
12
- # input :provider,
13
- # must: {
14
- # exist: -> provider { PROVIDERS.include?(provider) },
15
- # }
16
- # end
17
- module Conditionable
18
- def self.included(base)
19
- base.prepend(PrependedMethods)
3
+ # Add checks to your inputs, by calling lambdas with the name of you choice
4
+ # under the "must" key.
5
+ #
6
+ # Will raise an error if any check returns a truthy value.
7
+ #
8
+ # Example:
9
+ #
10
+ # class Pay < Actor
11
+ # input :provider,
12
+ # must: {
13
+ # exist: -> provider { PROVIDERS.include?(provider) },
14
+ # }
15
+ # end
16
+ #
17
+ # class Pay < Actor
18
+ # input :provider,
19
+ # must: {
20
+ # exist: {
21
+ # is: -> provider { PROVIDERS.include?(provider) },
22
+ # message: (lambda do |input_key:, check_name:, actor:, value:|
23
+ # "The specified provider \"#{value}\" was not found."
24
+ # end)
25
+ # }
26
+ # }
27
+ # end
28
+ module ServiceActor::Conditionable
29
+ def self.included(base)
30
+ base.prepend(PrependedMethods)
31
+ end
32
+
33
+ module PrependedMethods
34
+ DEFAULT_MESSAGE = lambda do |input_key:, check_name:, actor:, value:|
35
+ "The \"#{input_key}\" input on \"#{actor}\" must \"#{check_name}\" " \
36
+ "but was #{value.inspect}"
20
37
  end
21
38
 
22
- module PrependedMethods
23
- def _call
24
- self.class.inputs.each do |key, options|
25
- next unless options[:must]
39
+ private_constant :DEFAULT_MESSAGE
40
+
41
+ def _call # rubocop:disable Metrics/MethodLength
42
+ self.class.inputs.each do |key, options|
43
+ next unless options[:must]
44
+
45
+ options[:must].each do |check_name, check|
46
+ value = result[key]
47
+
48
+ check, message = define_check_from(check)
26
49
 
27
- options[:must].each do |name, check|
28
- value = result[key]
29
- next if check.call(value)
50
+ next if check.call(value)
30
51
 
31
- raise ArgumentError,
32
- "Input #{key} must #{name} but was #{value.inspect}"
33
- end
52
+ raise_error_with(
53
+ message,
54
+ input_key: key,
55
+ check_name: check_name,
56
+ actor: self.class,
57
+ value: value,
58
+ )
34
59
  end
60
+ end
61
+
62
+ super
63
+ end
64
+
65
+ private
35
66
 
36
- super
67
+ def define_check_from(check)
68
+ if check.is_a?(Hash)
69
+ check.values_at(:is, :message)
70
+ else
71
+ [check, DEFAULT_MESSAGE]
37
72
  end
38
73
  end
39
74
  end
@@ -1,59 +1,66 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ServiceActor
4
- module Core
5
- def self.included(base)
6
- base.extend(ClassMethods)
7
- end
3
+ module ServiceActor::Core
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
8
7
 
9
- module ClassMethods
10
- # Call an actor with a given result. Returns the result.
11
- #
12
- # CreateUser.call(name: "Joe")
13
- def call(result = nil, **arguments)
14
- result = Result.to_result(result).merge!(arguments)
15
- new(result)._call
16
- result
17
- end
18
-
19
- # Call an actor with arguments. Returns the result and does not raise on
20
- # failure.
21
- #
22
- # CreateUser.result(name: "Joe")
23
- def result(result = nil, **arguments)
24
- call(result, **arguments)
25
- rescue Failure => e
26
- e.result
27
- end
8
+ module ClassMethods
9
+ # Call an actor with a given result. Returns the result.
10
+ #
11
+ # CreateUser.call(name: "Joe")
12
+ def call(result = nil, **arguments)
13
+ result = ServiceActor::Result.to_result(result).merge!(arguments)
14
+ new(result)._call
15
+ result
28
16
  end
29
17
 
30
- # :nodoc:
31
- def initialize(result)
32
- @result = result
18
+ # Call an actor with arguments. Returns the result and does not raise on
19
+ # failure.
20
+ #
21
+ # CreateUser.result(name: "Joe")
22
+ def result(result = nil, **arguments)
23
+ call(result, **arguments)
24
+ rescue failure_class => e
25
+ e.result
33
26
  end
27
+ end
34
28
 
35
- # To implement in your actors.
36
- def call; end
29
+ # :nodoc:
30
+ def initialize(result)
31
+ @result = result
32
+ end
37
33
 
38
- # To implement in your actors.
39
- def rollback; end
34
+ # To implement in your actors.
35
+ def call; end
40
36
 
41
- # This method is used internally to override behavior on call. Overriding
42
- # `call` instead would mean that end-users have to call `super` in their
43
- # actors.
44
- # :nodoc:
45
- def _call
46
- call
47
- end
37
+ # To implement in your actors.
38
+ def rollback; end
39
+
40
+ # This method is used internally to override behavior on call. Overriding
41
+ # `call` instead would mean that end-users have to call `super` in their
42
+ # actors.
43
+ # :nodoc:
44
+ def _call
45
+ call
46
+ end
48
47
 
49
- protected
48
+ protected
50
49
 
51
- # Returns the current context from inside an actor.
52
- attr_reader :result
50
+ # Returns the current context from inside an actor.
51
+ attr_reader :result
53
52
 
54
- # Can be called from inside an actor to stop execution and mark as failed.
55
- def fail!(**arguments)
56
- result.fail!(**arguments)
57
- end
53
+ # Can be called from inside an actor to stop execution and mark as failed.
54
+ def fail!(**arguments)
55
+ result.fail!(self.class.failure_class, **arguments)
56
+ end
57
+
58
+ private
59
+
60
+ # Raises an error depending on the mode
61
+ def raise_error_with(message, **arguments)
62
+ message = message.call(**arguments) if message.is_a?(Proc)
63
+
64
+ raise self.class.argument_error_class, message
58
65
  end
59
66
  end
@@ -1,37 +1,76 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ServiceActor
4
- # Adds the `default:` option to inputs. Accepts regular values and lambdas.
5
- # If no default is set and the value has not been given, raises an error.
6
- #
7
- # Example:
8
- #
9
- # class MultiplyThing < Actor
10
- # input :counter, default: 1
11
- # input :multiplier, default: -> { rand(1..10) }
12
- # end
13
- module Defaultable
14
- def self.included(base)
15
- base.prepend(PrependedMethods)
16
- end
3
+ # Adds the `default:` option to inputs. Accepts regular values and lambdas.
4
+ # If no default is set and the value has not been given, raises an error.
5
+ #
6
+ # Example:
7
+ #
8
+ # class MultiplyThing < Actor
9
+ # input :counter, default: 1
10
+ # input :multiplier, default: -> { rand(1..10) }
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
+ module ServiceActor::Defaultable
29
+ def self.included(base)
30
+ base.prepend(PrependedMethods)
31
+ end
17
32
 
18
- module PrependedMethods
19
- def _call
20
- self.class.inputs.each do |name, input|
21
- next if result.key?(name)
33
+ module PrependedMethods
34
+ def _call # rubocop:disable Metrics/MethodLength
35
+ self.class.inputs.each do |key, input|
36
+ next if result.key?(key)
37
+
38
+ unless input.key?(:default)
39
+ raise_error_with(
40
+ "The \"#{key}\" input on \"#{self.class}\" is missing",
41
+ )
42
+ end
22
43
 
23
- if input.key?(:default)
24
- default = input[:default]
25
- default = default.call if default.is_a?(Proc)
26
- result[name] = default
27
- next
28
- end
44
+ default = input[:default]
29
45
 
30
- raise(ArgumentError, "Input #{name} on #{self.class} is missing")
46
+ if default.is_a?(Hash)
47
+ default_for_advanced_mode_with(result, key, default)
48
+ else
49
+ default_for_normal_mode_with(result, key, default)
31
50
  end
51
+ end
52
+
53
+ super
54
+ end
32
55
 
33
- super
56
+ private
57
+
58
+ def default_for_normal_mode_with(result, key, default)
59
+ default = default.call if default.is_a?(Proc)
60
+ result[key] = default
61
+ end
62
+
63
+ def default_for_advanced_mode_with(result, key, content)
64
+ default, message = content.values_at(:is, :message)
65
+
66
+ unless default
67
+ raise_error_with(message, input_key: key, actor: self.class)
34
68
  end
69
+
70
+ default = default.call if default.is_a?(Proc)
71
+ result[key] = default
72
+
73
+ message.call(key, self.class)
35
74
  end
36
75
  end
37
76
  end
@@ -1,6 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ServiceActor
4
- # Standard exception from which others inherit.
5
- class Error < StandardError; end
6
- end
3
+ # Standard exception from which others inherit.
4
+ class ServiceActor::Error < StandardError; end
@@ -1,40 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ServiceActor
4
- # Adds the `fail_on` DSL to actors. This allows you to call `.result` and get
5
- # back a failed actor instead of raising an exception.
6
- #
7
- # class ApplicationActor < Actor
8
- # fail_on ServiceActor::ArgumentError
9
- # end
10
- module Failable
11
- def self.included(base)
12
- base.extend(ClassMethods)
13
- base.prepend(PrependedMethods)
14
- end
3
+ # Adds the `fail_on` DSL to actors. This allows you to call `.result` and get
4
+ # back a failed actor instead of raising an exception.
5
+ #
6
+ # class ApplicationActor < Actor
7
+ # fail_on ServiceActor::ArgumentError
8
+ # end
9
+ module ServiceActor::Failable
10
+ def self.included(base)
11
+ base.extend(ClassMethods)
12
+ base.prepend(PrependedMethods)
13
+ end
15
14
 
16
- module ClassMethods
17
- def inherited(child)
18
- super
15
+ module ClassMethods
16
+ def inherited(child)
17
+ super
19
18
 
20
- child.fail_ons.push(*fail_ons)
21
- end
19
+ child.fail_ons.push(*fail_ons)
20
+ end
22
21
 
23
- def fail_on(*exceptions)
24
- fail_ons.push(*exceptions)
25
- end
22
+ def fail_on(*exceptions)
23
+ fail_ons.push(*exceptions)
24
+ end
26
25
 
27
- def fail_ons
28
- @fail_ons ||= []
29
- end
26
+ def fail_ons
27
+ @fail_ons ||= []
30
28
  end
29
+ end
31
30
 
32
- module PrependedMethods
33
- def _call
34
- super
35
- rescue *self.class.fail_ons => e
36
- fail!(error: e.message)
37
- end
31
+ module PrependedMethods
32
+ def _call
33
+ super
34
+ rescue *self.class.fail_ons => e
35
+ fail!(error: e.message)
38
36
  end
39
37
  end
40
38
  end
@@ -1,16 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ServiceActor
4
- # Error raised when using `fail!` inside an actor.
5
- class Failure < Error
6
- def initialize(result)
7
- @result = result
3
+ # Error raised when using `fail!` inside an actor.
4
+ class ServiceActor::Failure < ServiceActor::Error
5
+ def initialize(result)
6
+ @result = result
8
7
 
9
- error = result.respond_to?(:error) ? result.error : nil
8
+ error = result.respond_to?(:error) ? result.error : nil
10
9
 
11
- super(error)
12
- end
13
-
14
- attr_reader :result
10
+ super(error)
15
11
  end
12
+
13
+ attr_reader :result
16
14
  end
@@ -1,46 +1,88 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ServiceActor
4
- # Ensure your inputs and outputs are not nil by adding `allow_nil: false`.
5
- #
6
- # Example:
7
- #
8
- # class CreateUser < Actor
9
- # input :name, allow_nil: false
10
- # output :user, allow_nil: false
11
- # end
12
- module NilCheckable
13
- def self.included(base)
14
- base.prepend(PrependedMethods)
3
+ # Ensure your inputs and outputs are not nil by adding `allow_nil: false`.
4
+ #
5
+ # Example:
6
+ #
7
+ # class CreateUser < Actor
8
+ # input :name, allow_nil: false
9
+ # output :user, allow_nil: false
10
+ # end
11
+ #
12
+ # class CreateUser < Actor
13
+ # input :name,
14
+ # allow_nil: {
15
+ # is: false,
16
+ # message: (lambda do |origin:, input_key:, actor:|
17
+ # "The value \"#{input_key}\" cannot be empty"
18
+ # end)
19
+ # }
20
+ #
21
+ # input :phone, allow_nil: { is: false, message: "Phone must be present" }
22
+ #
23
+ # output :user,
24
+ # allow_nil: {
25
+ # is: false,
26
+ # message: (lambda do |origin:, input_key:, actor:|
27
+ # "The value \"#{input_key}\" cannot be empty"
28
+ # end)
29
+ # }
30
+ # end
31
+ module ServiceActor::NilCheckable
32
+ def self.included(base)
33
+ base.prepend(PrependedMethods)
34
+ end
35
+
36
+ module PrependedMethods
37
+ DEFAULT_MESSAGE = lambda do |origin:, input_key:, actor:|
38
+ "The \"#{input_key}\" #{origin} on \"#{actor}\" does not allow " \
39
+ "nil values"
15
40
  end
16
41
 
17
- module PrependedMethods
18
- def _call
19
- check_context_for_nil(self.class.inputs, origin: "input")
42
+ private_constant :DEFAULT_MESSAGE
20
43
 
21
- super
44
+ def _call
45
+ check_context_for_nil(self.class.inputs, origin: "input")
22
46
 
23
- check_context_for_nil(self.class.outputs, origin: "output")
24
- end
47
+ super
25
48
 
26
- private
49
+ check_context_for_nil(self.class.outputs, origin: "output")
50
+ end
27
51
 
28
- def check_context_for_nil(definitions, origin:)
29
- definitions.each do |name, options|
30
- next if !result[name].nil? || allow_nil?(options)
52
+ private
31
53
 
32
- raise ArgumentError,
33
- "The #{origin} \"#{name}\" on #{self.class} does not allow " \
34
- "nil values"
35
- end
36
- end
54
+ def check_context_for_nil(definitions, origin:)
55
+ definitions.each do |key, options|
56
+ value = result[key]
57
+
58
+ next unless value.nil?
59
+
60
+ allow_nil, message = define_allow_nil_from(options[:allow_nil])
37
61
 
38
- def allow_nil?(options)
39
- return options[:allow_nil] if options.key?(:allow_nil)
40
- return true if options.key?(:default) && options[:default].nil?
62
+ next if allow_nil?(allow_nil, options)
41
63
 
42
- !options[:type]
64
+ raise_error_with(
65
+ message,
66
+ origin: origin,
67
+ input_key: key,
68
+ actor: self.class,
69
+ )
43
70
  end
44
71
  end
72
+
73
+ def define_allow_nil_from(allow_nil)
74
+ if allow_nil.is_a?(Hash)
75
+ allow_nil.values_at(:is, :message)
76
+ else
77
+ [allow_nil, DEFAULT_MESSAGE]
78
+ end
79
+ end
80
+
81
+ def allow_nil?(allow_nil, options)
82
+ return allow_nil unless allow_nil.nil?
83
+ return true if options.key?(:default) && options[:default].nil?
84
+
85
+ !options[:type]
86
+ end
45
87
  end
46
88
  end