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,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