service_actor 1.0.0 → 3.1.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,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Actor
4
- # DSL to call a series of actors with the same context. On failure, calls
5
- # rollback on any actor that succeeded.
3
+ module ServiceActor
4
+ # Play class method to call a series of actors with the same result. On
5
+ # failure, calls rollback on any actor that succeeded.
6
6
  #
7
7
  # class CreateUser < Actor
8
8
  # play SaveUser,
@@ -38,11 +38,11 @@ class Actor
38
38
  module PrependedMethods
39
39
  def call
40
40
  self.class.play_actors.each do |options|
41
- next if options[:if] && !options[:if].call(@context)
41
+ next if options[:if] && !options[:if].call(result)
42
42
 
43
43
  play_actor(options[:actor])
44
44
  end
45
- rescue Actor::Failure
45
+ rescue Failure
46
46
  rollback
47
47
  raise
48
48
  end
@@ -60,11 +60,12 @@ class Actor
60
60
  private
61
61
 
62
62
  def play_actor(actor)
63
- if actor.respond_to?(:new)
64
- actor = actor.new(@context)
65
- actor.run
63
+ if actor.is_a?(Class) && actor.ancestors.include?(Actor)
64
+ actor = actor.new(result)
65
+ actor._call
66
66
  else
67
- actor.call(@context)
67
+ new_result = actor.call(result)
68
+ result.merge!(new_result.to_h) if new_result.respond_to?(:to_h)
68
69
  end
69
70
 
70
71
  (@played ||= []).unshift(actor)
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ostruct'
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
14
+
15
+ def inspect
16
+ "<#{self.class.name} #{to_h}>"
17
+ end
18
+
19
+ def fail!(result = {})
20
+ merge!(result)
21
+ merge!(failure?: true)
22
+
23
+ raise Failure, self
24
+ end
25
+
26
+ def success?
27
+ !failure?
28
+ end
29
+
30
+ def failure?
31
+ self[:failure?] || false
32
+ end
33
+
34
+ def merge!(result)
35
+ result.each_pair do |key, value|
36
+ self[key] = value
37
+ end
38
+
39
+ self
40
+ end
41
+
42
+ def key?(name)
43
+ to_h.key?(name)
44
+ end
45
+
46
+ def [](name)
47
+ to_h[name]
48
+ end
49
+
50
+ # Redefined here to override the method on `Object`.
51
+ def display
52
+ to_h.fetch(:display)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
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)
17
+ end
18
+
19
+ module PrependedMethods
20
+ def _call
21
+ check_type_definitions(self.class.inputs, kind: 'Input')
22
+
23
+ super
24
+
25
+ check_type_definitions(self.class.outputs, kind: 'Output')
26
+ end
27
+
28
+ private
29
+
30
+ def check_type_definitions(definitions, kind:)
31
+ definitions.each do |key, options|
32
+ type_definition = options[:type] || next
33
+ value = result[key] || next
34
+
35
+ types = types_for_definition(type_definition)
36
+ next if types.any? { |type| value.is_a?(type) }
37
+
38
+ raise ArgumentError,
39
+ "#{kind} #{key} on #{self.class} must be of type " \
40
+ "#{types.join(', ')} but was #{value.class}"
41
+ end
42
+ end
43
+
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
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ServiceActor
4
+ VERSION = '3.1.1'
5
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: service_actor
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 3.1.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: 2020-03-15 00:00:00.000000000 Z
11
+ date: 2021-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -66,6 +66,62 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop-rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-performance
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: code-scanning-rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: interactor
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
69
125
  description: Service objects for your application logic
70
126
  email:
71
127
  - sunny@sunfox.org
@@ -77,25 +133,29 @@ extra_rdoc_files:
77
133
  files:
78
134
  - LICENSE.txt
79
135
  - README.md
80
- - lib/actor/attributable.rb
81
- - lib/actor/conditionable.rb
82
- - lib/actor/context.rb
83
- - lib/actor/defaultable.rb
84
- - lib/actor/failure.rb
85
- - lib/actor/filtered_context.rb
86
- - lib/actor/playable.rb
87
- - lib/actor/requireable.rb
88
- - lib/actor/success.rb
89
- - lib/actor/type_checkable.rb
90
- - lib/actor/version.rb
91
136
  - lib/service_actor.rb
137
+ - lib/service_actor/argument_error.rb
138
+ - lib/service_actor/attributable.rb
139
+ - lib/service_actor/base.rb
140
+ - lib/service_actor/collectionable.rb
141
+ - lib/service_actor/conditionable.rb
142
+ - lib/service_actor/core.rb
143
+ - lib/service_actor/defaultable.rb
144
+ - lib/service_actor/error.rb
145
+ - lib/service_actor/failable.rb
146
+ - lib/service_actor/failure.rb
147
+ - lib/service_actor/nil_checkable.rb
148
+ - lib/service_actor/playable.rb
149
+ - lib/service_actor/result.rb
150
+ - lib/service_actor/type_checkable.rb
151
+ - lib/service_actor/version.rb
92
152
  homepage: https://github.com/sunny/actor
93
153
  licenses:
94
154
  - MIT
95
155
  metadata:
96
156
  homepage_uri: https://github.com/sunny/actor
97
157
  source_code_uri: https://github.com/sunny/actor
98
- changelog_uri: https://github.com/sunny/actor/blob/master/CHANGELOG.md
158
+ changelog_uri: https://github.com/sunny/actor/blob/main/CHANGELOG.md
99
159
  post_install_message:
100
160
  rdoc_options: []
101
161
  require_paths:
@@ -104,14 +164,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
104
164
  requirements:
105
165
  - - ">="
106
166
  - !ruby/object:Gem::Version
107
- version: '0'
167
+ version: '2.4'
108
168
  required_rubygems_version: !ruby/object:Gem::Requirement
109
169
  requirements:
110
170
  - - ">="
111
171
  - !ruby/object:Gem::Version
112
172
  version: '0'
113
173
  requirements: []
114
- rubygems_version: 3.1.2
174
+ rubygems_version: 3.1.6
115
175
  signing_key:
116
176
  specification_version: 4
117
177
  summary: Service objects for your application logic
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Actor
4
- # Add boolean checks to inputs, by calling lambdas starting with `must*`.
5
- #
6
- # Example:
7
- #
8
- # class Pay < Actor
9
- # input :provider,
10
- # must: {
11
- # exist: ->(provider) { PROVIDERS.include?(provider) }
12
- # }
13
- #
14
- # output :user, required: true
15
- # end
16
- module Conditionable
17
- def before
18
- super
19
-
20
- self.class.inputs.each do |key, options|
21
- next unless options[:must]
22
-
23
- options[:must].each do |name, check|
24
- value = @context[key]
25
- next if check.call(value)
26
-
27
- name = name.to_s.sub(/^must_/, '')
28
- raise ArgumentError,
29
- "Input #{key} must #{name} but was #{value.inspect}."
30
- end
31
- end
32
- end
33
- end
34
- end
data/lib/actor/context.rb DELETED
@@ -1,92 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Actor
4
- # Represents the result of an action.
5
- class Context
6
- def self.to_context(data)
7
- return data if data.is_a?(self)
8
-
9
- new(data)
10
- end
11
-
12
- def initialize(data = {})
13
- @data = data.dup
14
- end
15
-
16
- def ==(other)
17
- other.class == self.class && data == other.data
18
- end
19
-
20
- def inspect
21
- "<ActorContext #{data.inspect}>"
22
- end
23
-
24
- def fail!(context = {})
25
- merge!(context)
26
- data[:failure?] = true
27
- raise Actor::Failure, self
28
- end
29
-
30
- def succeed!(context = {})
31
- merge!(context)
32
- data[:failure?] = false
33
- raise Actor::Success, self
34
- end
35
-
36
- def success?
37
- !failure?
38
- end
39
-
40
- def failure?
41
- data.fetch(:failure?, false)
42
- end
43
-
44
- def merge!(context)
45
- data.merge!(context)
46
-
47
- self
48
- end
49
-
50
- def key?(name)
51
- data.key?(name)
52
- end
53
-
54
- def [](name)
55
- data[name]
56
- end
57
-
58
- # Redefined here to override the method on `Object`.
59
- def display
60
- data.fetch(:display)
61
- end
62
-
63
- protected
64
-
65
- attr_reader :data
66
-
67
- private
68
-
69
- # rubocop:disable Style/MethodMissingSuper
70
- def method_missing(name, *arguments, **)
71
- if name =~ /=$/
72
- key = name.to_s.sub('=', '').to_sym
73
- data[key] = arguments.first
74
- else
75
- data[name]
76
- end
77
- end
78
- # rubocop:enable Style/MethodMissingSuper
79
-
80
- def respond_to_missing?(*_arguments)
81
- true
82
- end
83
-
84
- def context_get(key)
85
- data[key]
86
- end
87
-
88
- def context_set(key, value)
89
- data[key] = value
90
- end
91
- end
92
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Actor
4
- # Adds the `default:` option to inputs. Accepts regular values and lambdas.
5
- #
6
- # Example:
7
- #
8
- # class MultiplyThing < Actor
9
- # input :counter, default: 1
10
- # input :multiplier, default: -> { rand(1..10) }
11
- # end
12
- module Defaultable
13
- def before
14
- self.class.inputs.each do |name, input|
15
- next if !input.key?(:default) || @context.key?(name)
16
-
17
- default = input[:default]
18
- default = default.call if default.respond_to?(:call)
19
- @context.merge!(name => default)
20
- end
21
-
22
- super
23
- end
24
- end
25
- end