service_actor 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a16f2ccd8bf0d2e83d5cb9ec163c81b9df3be06515cb716138049f9d33420759
4
+ data.tar.gz: bfb6c4dca34cb64fa3c8ce84b085faa85547c1a554f241ccf49d92a7a67d012b
5
+ SHA512:
6
+ metadata.gz: 18df62d093865eefd2acd8a9d3567c1379be5c097c087b6c2d2eee1dc8cf0485e5b0b6f712b11a19492eca3465b48ba0281de7b88130e170681923ec3cc2086c
7
+ data.tar.gz: 80df1b4cc8269802cfe217663fa16ed907103a1b3e4b8ee42d1d01ac08c47a571f4993439f27d1065fbbb38738132be459b042aadfd04bdfe229bb6a2a4dbc27
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Sunny Ripert
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,298 @@
1
+ # Actor
2
+
3
+ ![Tests](https://github.com/sunny/actor/workflows/Tests/badge.svg)
4
+
5
+ Ruby service objects. Lets you move your application logic into small
6
+ building blocs to keep your controllers and your models thin.
7
+
8
+ ## Installation
9
+
10
+ Add these lines to your application's Gemfile:
11
+
12
+ ```rb
13
+ # Service objects to keep the business logic
14
+ gem 'service_actor'
15
+ ```
16
+
17
+
18
+ ## Usage
19
+
20
+ Actors are single-purpose actions in your application that represent your
21
+ business logic. They start with a verb, inherit from `Actor` and implement a
22
+ `call` method.
23
+
24
+ ```rb
25
+ # app/actors/send_notification.rb
26
+ class SendNotification < Actor
27
+ def call
28
+ # …
29
+ end
30
+ end
31
+ ```
32
+
33
+ Use `.call` to use them in your application:
34
+
35
+ ```rb
36
+ SendNotification.call
37
+ ```
38
+
39
+ ### Inputs
40
+
41
+ Actors can accept arguments with `input`:
42
+
43
+ ```rb
44
+ class GreetUser < Actor
45
+ input :user
46
+
47
+ def call
48
+ puts "Hello #{user.name}!"
49
+ end
50
+ end
51
+ ```
52
+
53
+ And receive them as arguments to `call`:
54
+
55
+ ```rb
56
+ GreetUser.call(user: User.first)
57
+ ```
58
+
59
+ ### Outputs
60
+
61
+ Use `output` to declare what your actor can return, then assign them to your
62
+ context.
63
+
64
+ ```rb
65
+ class BuildGreeting < Actor
66
+ output :greeting
67
+
68
+ def call
69
+ context.greeting = "Have a wonderful day!"
70
+ end
71
+ end
72
+ ```
73
+
74
+ Calling an actor returns a context:
75
+
76
+ ```rb
77
+ result = BuildGreeting.call
78
+ result.greeting # => "Have a wonderful day!"
79
+ ```
80
+
81
+ ### Defaults
82
+
83
+ Inputs can have defaults:
84
+
85
+ ```rb
86
+ class PrintWelcome < Actor
87
+ input :user
88
+ input :adjective, default: "wonderful"
89
+ input :length_of_time, default: -> { ["day", "week", "month"].sample }
90
+
91
+ output :greeting
92
+
93
+ def call
94
+ context.greeting = "Hello #{name}! Have a #{adjective} #{length_of_time}!"
95
+ end
96
+ end
97
+ ```
98
+
99
+ ### Types
100
+
101
+ Inputs can define a type, or an array of possible types it must match:
102
+
103
+ ```rb
104
+ class UpdateUser < Actor
105
+ input :user, type: 'User'
106
+ input :age, type: %w[Integer Float]
107
+
108
+ # …
109
+ end
110
+ ```
111
+
112
+ ### Conditions
113
+
114
+ If types don't cut it, you can add small conditions with the name of your choice
115
+ under `must`:
116
+
117
+ ```rb
118
+ class UpdateAdminUser < Actor
119
+ input :user,
120
+ must: {
121
+ be_an_admin: ->(user) { user.admin? }
122
+ }
123
+ end
124
+ ```
125
+
126
+ ### Result
127
+
128
+ All actors are successful by default. To stop its execution and mark is as
129
+ having failed, use `fail!`:
130
+
131
+ ```rb
132
+ class UpdateUser
133
+ input :user
134
+ input :attributes
135
+
136
+ def call
137
+ user.attributes = attributes
138
+
139
+ fail!(error: "Invalid user") unless user.valid?
140
+
141
+ # …
142
+ end
143
+ end
144
+ ```
145
+
146
+ You can then test for the success by calling your actor with `.result` instead
147
+ of `.call`. This will let you test for `success?` or `failure?` on the context
148
+ instead of raising an exception.
149
+
150
+ For example in a Rails controller:
151
+
152
+ ```rb
153
+ # app/controllers/users_controller.rb
154
+ class UsersController < ApplicationController
155
+ def create
156
+ result = UpdateUser.result(user: user, attributes: user_attributes)
157
+ if result.success?
158
+ redirect_to result.user
159
+ else
160
+ render :new, notice: result.error
161
+ end
162
+ end
163
+ end
164
+ ```
165
+
166
+ ### Play
167
+
168
+ An actor can call actors in sequence by using `play`. Each actor will hand over
169
+ the context to the next actor.
170
+
171
+ ```rb
172
+ class PlaceOrder < Actor
173
+ play CreateOrder,
174
+ Pay,
175
+ SendOrderConfirmation,
176
+ NotifyAdmins
177
+ end
178
+ ```
179
+
180
+ ### Rollback
181
+
182
+ When using `play`, if one of the actors calls `fail!`, the following actors will
183
+ not be called.
184
+
185
+ Also, any _previous_ actor that succeeded will call the `rollback` method, if
186
+ you defined one.
187
+
188
+ ```rb
189
+ class CreateOrder < Actor
190
+ def call
191
+ context.order = Order.create!(…)
192
+ end
193
+
194
+ def rollback
195
+ context.order.destroy
196
+ end
197
+ end
198
+ ```
199
+
200
+ ### Early success
201
+
202
+ When using `play` you can use `succeed!` so that the following actors will not
203
+ be called, but still consider the actor to be successful.
204
+
205
+ ### Lambdas
206
+
207
+ You can call inline actions using lambdas:
208
+
209
+ ```rb
210
+ class Pay
211
+ play ->(ctx) { ctx.payment_provider = "stripe" },
212
+ CreatePayment,
213
+ ->(ctx) { ctx.user_to_notify = ctx.payment.user },
214
+ SendNotification
215
+ end
216
+ ```
217
+
218
+ ### Before, after and around
219
+
220
+ To do actions before or after actors, use lambdas or simply override `call` and
221
+ use `super`. For example:
222
+
223
+ ```rb
224
+ class Pay
225
+ # …
226
+
227
+ def call
228
+ Time.with_timezone('Paris') do
229
+ super
230
+ end
231
+ end
232
+ end
233
+ ```
234
+
235
+ ### Play conditions
236
+
237
+ Some actors in a play can be called conditionaly:
238
+
239
+ ```rb
240
+ class PlaceOrder < Actor
241
+ play CreateOrder,
242
+ Pay
243
+ play NotifyAdmins, if: ->(ctx) { ctx.order.amount > 42 }
244
+ end
245
+ ```
246
+
247
+ ## Influences
248
+
249
+ This gem is heavily influenced by
250
+ [Interactor](https://github.com/collectiveidea/interactor) ♥.
251
+ However there a a few key differences which make `actor` unique:
252
+
253
+ - Defaults to raising errors on failures. Actor uses `call` and `result` instead of `call!` and `call`. This way, the default is to raise an error and failures are not hidden away.
254
+ - Does not [hide errors when an actor fails inside another actor](https://github.com/collectiveidea/interactor/issues/170).
255
+ - You can use lambdas inside organizers.
256
+ - Requires you to document the arguments with `input` and `output`.
257
+ - Type checking of inputs and outputs.
258
+ - Inputs and outputs can be required.
259
+ - Defaults for inputs.
260
+ - Conditions on inputs.
261
+ - Shorter fail syntax: `fail!` vs `context.fail!`.
262
+ - Trigger early success in organisers with `succeed!`.
263
+ - Shorter setup syntax: inherit from `< Actor` vs having to `include Interactor` or `include Interactor::Organizer`.
264
+ - Multiple organizers.
265
+ - Conditions inside organizers.
266
+ - No `before`, `after` and `around` hooks. Prefer simply overriding `call` with `super` which allows wrapping the whole method.
267
+ - [Does not rely on `OpenStruct`](https://github.com/collectiveidea/interactor/issues/183)
268
+ - Does not print warnings on Ruby 2.7.
269
+
270
+
271
+ ## Development
272
+
273
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
274
+ `rake` to run the tests. You can also run `bin/console` for an interactive
275
+ prompt that will allow you to experiment.
276
+
277
+ To install this gem onto your local machine, run `bundle exec rake install`.
278
+ To release a new version, update the version number in `version.rb`, and then
279
+ run `bundle exec rake release`, which will create a git tag for the version,
280
+ push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
281
+
282
+ ## Contributing
283
+
284
+ Bug reports and pull requests are welcome on GitHub at
285
+ https://github.com/sunny/actor. This project is intended to be a safe,
286
+ welcoming space for collaboration, and contributors are expected to adhere to
287
+ the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
288
+
289
+ ## License
290
+
291
+ The gem is available as open source under the terms of the
292
+ [MIT License](https://opensource.org/licenses/MIT).
293
+
294
+ ## Code of Conduct
295
+
296
+ Everyone interacting in the Test project’s codebases, issue trackers, chat
297
+ rooms and mailing lists is expected to follow the
298
+ [code of conduct](https://github.com/sunny/actor/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Actor
4
+ # DSL to document the accepted attributes.
5
+ #
6
+ # class CreateUser < Actor
7
+ # input :name
8
+ # output :name
9
+ # end
10
+ module Attributable
11
+ def self.included(base)
12
+ base.extend(ClassMethods)
13
+ base.prepend(PrependedMethods)
14
+ end
15
+
16
+ module ClassMethods
17
+ def inherited(child)
18
+ super
19
+
20
+ child.inputs.merge!(inputs)
21
+ child.outputs.merge!(outputs)
22
+ end
23
+
24
+ def input(name, **arguments)
25
+ inputs[name] = arguments
26
+
27
+ define_method(name) do
28
+ context.public_send(name)
29
+ end
30
+
31
+ private name
32
+ end
33
+
34
+ def inputs
35
+ @inputs ||= {}
36
+ end
37
+
38
+ def output(name, **arguments)
39
+ outputs[name] = arguments
40
+ end
41
+
42
+ def outputs
43
+ @outputs ||= { error: { type: 'String' } }
44
+ end
45
+ end
46
+
47
+ module PrependedMethods
48
+ # rubocop:disable Naming/MemoizedInstanceVariableName
49
+ def context
50
+ @filtered_context ||= Actor::FilteredContext.new(
51
+ super,
52
+ readers: self.class.inputs.keys,
53
+ setters: self.class.outputs.keys,
54
+ )
55
+ end
56
+ # rubocop:enable Naming/MemoizedInstanceVariableName
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,34 @@
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
@@ -0,0 +1,92 @@
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
@@ -0,0 +1,25 @@
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
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Actor
4
+ # Error raised when using `fail!` inside an actor.
5
+ class Failure < StandardError
6
+ def initialize(context)
7
+ @context = context
8
+
9
+ error = context.respond_to?(:error) ? context.error : nil
10
+
11
+ super(error)
12
+ end
13
+
14
+ attr_reader :context
15
+ end
16
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Actor
4
+ # Represents the result of an action, tied to inputs and outputs.
5
+ class FilteredContext
6
+ def initialize(context, readers:, setters:)
7
+ @context = context
8
+ @readers = readers
9
+ @setters = setters
10
+ end
11
+
12
+ def inspect
13
+ "<#{self.class.name} #{context.inspect} " \
14
+ "readers: #{readers.inspect} " \
15
+ "setters: #{setters.inspect}>"
16
+ end
17
+
18
+ def fail!(**arguments)
19
+ context.fail!(**arguments)
20
+ end
21
+
22
+ def succeed!(**arguments)
23
+ context.fail!(**arguments)
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :context, :readers, :setters
29
+
30
+ def method_missing(name, *arguments, **options)
31
+ return super unless context.respond_to?(name)
32
+
33
+ unless available_methods.include?(name)
34
+ raise ArgumentError, "Cannot call #{name} on #{inspect}"
35
+ end
36
+
37
+ context.public_send(name, *arguments)
38
+ end
39
+
40
+ def respond_to_missing?(name, *_arguments)
41
+ available_methods.include?(name)
42
+ end
43
+
44
+ def available_methods
45
+ @available_methods ||=
46
+ readers + setters.map { |key| "#{key}=".to_sym }
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
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.
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
17
+
18
+ module ClassMethods
19
+ def inherited(child)
20
+ super
21
+
22
+ play_actors.each do |actor|
23
+ child.play_actors << actor
24
+ end
25
+ end
26
+
27
+ def play(*actors, **options)
28
+ actors.each do |actor|
29
+ play_actors.push({ actor: actor, **options })
30
+ end
31
+ end
32
+
33
+ def play_actors
34
+ @play_actors ||= []
35
+ end
36
+ end
37
+
38
+ module PrependedMethods
39
+ def call
40
+ self.class.play_actors.each do |options|
41
+ next if options[:if] && !options[:if].call(@context)
42
+
43
+ play_actor(options[:actor])
44
+ end
45
+ rescue Actor::Failure
46
+ rollback
47
+ raise
48
+ end
49
+
50
+ def rollback
51
+ return unless @played
52
+
53
+ @played.each do |actor|
54
+ next unless actor.respond_to?(:rollback)
55
+
56
+ actor.rollback
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def play_actor(actor)
63
+ if actor.respond_to?(:new)
64
+ actor = actor.new(@context)
65
+ actor.run
66
+ else
67
+ actor.call(@context)
68
+ end
69
+
70
+ (@played ||= []).unshift(actor)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Actor
4
+ # Adds `required:` checking to inputs and outputs.
5
+ #
6
+ # Example:
7
+ #
8
+ # class CreateUser < Actor
9
+ # input :name, required: true
10
+ # output :user, required: true
11
+ # end
12
+ module Requireable
13
+ def before
14
+ super
15
+
16
+ check_required_definitions(self.class.inputs, kind: 'Input')
17
+ end
18
+
19
+ def after
20
+ super
21
+
22
+ check_required_definitions(self.class.outputs, kind: 'Output')
23
+ end
24
+
25
+ private
26
+
27
+ def check_required_definitions(definitions, kind:)
28
+ definitions.each do |key, options|
29
+ next unless options[:required] && @context[key].nil?
30
+
31
+ raise ArgumentError,
32
+ "#{kind} #{key} on #{self.class} is required but was nil."
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Actor
4
+ # Raised when using `succeed!` to halt the progression of an organizer.
5
+ class Success < StandardError; end
6
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Actor
4
+ # Adds `type:` checking to inputs and outputs. Accepts strings that should
5
+ # match an ancestor. Also accepts arrays.
6
+ #
7
+ # Example:
8
+ #
9
+ # class ReduceOrderAmount < Actor
10
+ # input :order, type: 'Order'
11
+ # input :amount, type: %w[Integer Float]
12
+ # input :bonus_applied, type: %w[TrueClass FalseClass]
13
+ # end
14
+ module TypeCheckable
15
+ def before
16
+ super
17
+
18
+ check_type_definitions(self.class.inputs, kind: 'Input')
19
+ end
20
+
21
+ def after
22
+ super
23
+
24
+ check_type_definitions(self.class.outputs, kind: 'Output')
25
+ end
26
+
27
+ private
28
+
29
+ def check_type_definitions(definitions, kind:)
30
+ definitions.each do |key, options|
31
+ type_definition = options[:type] || next
32
+ value = @context[key] || next
33
+
34
+ types = Array(type_definition).map { |name| Object.const_get(name) }
35
+ next if types.any? { |type| value.is_a?(type) }
36
+
37
+ error = "#{kind} #{key} on #{self.class} must be of type " \
38
+ "#{types.join(', ')} but was #{value.class}"
39
+ raise ArgumentError, error
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Actor
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'actor/failure'
4
+ require 'actor/success'
5
+ require 'actor/context'
6
+ require 'actor/filtered_context'
7
+
8
+ require 'actor/playable'
9
+ require 'actor/attributable'
10
+ require 'actor/defaultable'
11
+ require 'actor/type_checkable'
12
+ require 'actor/requireable'
13
+ require 'actor/conditionable'
14
+
15
+ # Actors should start with a verb, inherit from Actor and implement a `call`
16
+ # method.
17
+ class Actor
18
+ include Attributable
19
+ include Playable
20
+ prepend Defaultable
21
+ prepend TypeCheckable
22
+ prepend Requireable
23
+ prepend Conditionable
24
+
25
+ class << self
26
+ # Call an actor with a given context. Returns the context.
27
+ #
28
+ # CreateUser.call(name: 'Joe')
29
+ def call(context = {}, **arguments)
30
+ context = Actor::Context.to_context(context).merge!(arguments)
31
+ new(context).run
32
+ context
33
+ rescue Actor::Success
34
+ context
35
+ end
36
+
37
+ alias call! call
38
+
39
+ # Call an actor with a given context. Returns the context and does not raise
40
+ # on failure.
41
+ #
42
+ # CreateUser.result(name: 'Joe')
43
+ def result(context = {}, **arguments)
44
+ call(context, **arguments)
45
+ rescue Actor::Failure => e
46
+ e.context
47
+ end
48
+ end
49
+
50
+ # :nodoc:
51
+ def initialize(context)
52
+ @context = context
53
+ end
54
+
55
+ # To implement in your actors.
56
+ def call; end
57
+
58
+ # To implement in your actors.
59
+ def rollback; end
60
+
61
+ # :nodoc:
62
+ def before; end
63
+
64
+ # :nodoc:
65
+ def after; end
66
+
67
+ # :nodoc:
68
+ def run
69
+ before
70
+ call
71
+ after
72
+ end
73
+
74
+ private
75
+
76
+ # Returns the current context from inside an actor.
77
+ attr_reader :context
78
+
79
+ # Can be called from inside an actor to stop execution and mark as failed.
80
+ def fail!(**arguments)
81
+ @context.fail!(**arguments)
82
+ end
83
+
84
+ # Can be called from inside an actor to stop execution early.
85
+ def succeed!(**arguments)
86
+ @context.succeed!(**arguments)
87
+ end
88
+ end
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: service_actor
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Sunny Ripert
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-03-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Service objects for your application logic
70
+ email:
71
+ - sunny@sunfox.org
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files:
75
+ - LICENSE.txt
76
+ - README.md
77
+ files:
78
+ - LICENSE.txt
79
+ - 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
+ - lib/service_actor.rb
92
+ homepage: https://github.com/sunny/actor
93
+ licenses:
94
+ - MIT
95
+ metadata:
96
+ homepage_uri: https://github.com/sunny/actor
97
+ source_code_uri: https://github.com/sunny/actor
98
+ changelog_uri: https://github.com/sunny/actor/blob/master/CHANGELOG.md
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubygems_version: 3.1.2
115
+ signing_key:
116
+ specification_version: 4
117
+ summary: Service objects for your application logic
118
+ test_files: []