interaktor 0.4.0 → 0.5.0
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.
- checksums.yaml +4 -4
- data/.github/workflows/tests.yml +1 -1
- data/.ruby-version +1 -1
- data/.standard.yml +1 -0
- data/Gemfile +2 -5
- data/README.md +25 -4
- data/bin/_guard-core +16 -0
- data/bin/guard +16 -0
- data/bin/rspec +16 -0
- data/bin/standardrb +16 -0
- data/interaktor.gemspec +3 -4
- data/lib/interaktor/callable.rb +63 -76
- data/lib/interaktor/error/invalid_method_for_state_error.rb +10 -0
- data/lib/interaktor/error/organizer_missing_passed_attribute_error.rb +1 -1
- data/lib/interaktor/error/organizer_success_attribute_missing_error.rb +1 -1
- data/lib/interaktor/error/unknown_attribute_error.rb +16 -0
- data/lib/interaktor/failure.rb +8 -8
- data/lib/interaktor/hooks.rb +53 -3
- data/lib/interaktor/interaction.rb +154 -0
- data/lib/interaktor/organizer.rb +9 -1
- data/lib/interaktor.rb +41 -53
- data/spec/integration_spec.rb +1874 -1874
- data/spec/interaktor/context_spec.rb +185 -185
- data/spec/interaktor/hooks_spec.rb +17 -17
- data/spec/interaktor/organizer_spec.rb +58 -90
- data/spec/support/helpers.rb +5 -7
- data/spec/support/lint.rb +33 -60
- metadata +20 -22
- data/.rubocop.yml +0 -245
- data/lib/interaktor/context.rb +0 -116
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6937b87e9844c597a62670ffe90e7d2abba7a7559bc42254d195973e685b109f
|
|
4
|
+
data.tar.gz: 2d6f4b42e049de1d271b42118118a1a847f7fb71f21ce03c87394549af710419
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3c68b887ff7cd0888d165ef5468029225b20f046f339bbea9184cd072ee221d7a1bb7bf37dbfff8b24c06ffc17c11839333172000d277d17a07307d788ad2530
|
|
7
|
+
data.tar.gz: 3b6546acf884222f52472887ddd2af0439a891486aa542a7410ef787ed7b8ac2e662c728068d7f2852ee8cc3bf83610fa4c3f97410c19ac764d04a6a81457575
|
data/.github/workflows/tests.yml
CHANGED
|
@@ -8,7 +8,7 @@ jobs:
|
|
|
8
8
|
fail-fast: false
|
|
9
9
|
matrix:
|
|
10
10
|
os: [ubuntu-latest, macos-latest]
|
|
11
|
-
ruby: [
|
|
11
|
+
ruby: [3.0, 3.1, 3.2, 3.3, 3.4, head, debug]
|
|
12
12
|
runs-on: ${{ matrix.os }}
|
|
13
13
|
continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' }}
|
|
14
14
|
env:
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.4.7
|
data/.standard.yml
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ruby_version: 3.0
|
data/Gemfile
CHANGED
|
@@ -2,12 +2,9 @@ source "https://rubygems.org"
|
|
|
2
2
|
|
|
3
3
|
gemspec
|
|
4
4
|
|
|
5
|
+
gem "debug"
|
|
5
6
|
gem "guard-rspec", require: false
|
|
6
|
-
gem "
|
|
7
|
-
gem "rubocop-performance"
|
|
8
|
-
gem "rubocop-rspec"
|
|
9
|
-
gem "rufo", "~> 0.12.0"
|
|
10
|
-
gem "solargraph"
|
|
7
|
+
gem "standardrb"
|
|
11
8
|
|
|
12
9
|
group :test do
|
|
13
10
|
gem "pry-byebug", platforms: [:mri]
|
data/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](http://rubygems.org/gems/interaktor)
|
|
4
4
|
[](https://travis-ci.org/taylorthurlow/interaktor)
|
|
5
5
|
|
|
6
|
-
**DISCLAIMER: Interaktor is
|
|
6
|
+
**DISCLAIMER: Interaktor is considered to be stable, but has not yet reached version 1.0. Following semantic versioning, minor version updates can introduce breaking changes. Please review the changelog when updating.**
|
|
7
7
|
|
|
8
8
|
**Interaktor** is a fork of [Interactor by collectiveidea](https://github.com/collectiveidea/interactor). While Interactor is still used by collectiveidea internally, communication and progress has been slow in adapting to pull requests and issues. This inactivity combined with my desire to dial back on the Interactor's inherent permissivity led me to fork it and create Interaktor.
|
|
9
9
|
|
|
@@ -66,7 +66,7 @@ CreateUser.call(name: "Foo Bar")
|
|
|
66
66
|
|
|
67
67
|
Based on the outcome of the interaktor's work, we can require certain attributes. In the example below, we must succeed with a `user_id` attribute, and if we fail, we must provide an `error_messages` attribute.
|
|
68
68
|
|
|
69
|
-
The use of `#success!` allows you to early-return from an interaktor's work. If no `success` attribute is provided, and the `call` method finishes execution normally, then the interaktor is considered to
|
|
69
|
+
The use of `#success!` allows you to early-return from an interaktor's work. If no `success` attribute is provided, and the `call` method finishes execution normally, then the interaktor is considered to to have completed successfully.
|
|
70
70
|
|
|
71
71
|
```ruby
|
|
72
72
|
class CreateUser
|
|
@@ -108,7 +108,7 @@ end
|
|
|
108
108
|
|
|
109
109
|
`#fail!` always throws an exception of type `Interaktor::Failure`.
|
|
110
110
|
|
|
111
|
-
Normally, however, these exceptions are not seen. In the recommended usage, the caller invokes the interaktor using the class method `.call`, then checks the `#success?` method of the returned object. This works because the `call` class method
|
|
111
|
+
Normally, however, these exceptions are not seen. In the recommended usage, the caller invokes the interaktor using the class method `.call`, then checks the `#success?` method of the returned object. This works because the `call` class method rescues the `Interaktor::Failure` exception. When unit testing an interaktor, if calling custom business logic methods directly and bypassing `call`, be aware that `fail!` will generate such exceptions.
|
|
112
112
|
|
|
113
113
|
See _Interaktors in the controller_, below, for the recommended usage of `.call` and `#success?`.
|
|
114
114
|
|
|
@@ -144,6 +144,16 @@ after do
|
|
|
144
144
|
end
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
+
#### Ensure hooks
|
|
148
|
+
|
|
149
|
+
Very similar to `after` hooks, but the hooks are run in an `ensure` block in the order they are defined.
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
ensure_hook do
|
|
153
|
+
file.close
|
|
154
|
+
end
|
|
155
|
+
```
|
|
156
|
+
|
|
147
157
|
#### Around hooks
|
|
148
158
|
|
|
149
159
|
You can also define around hooks in the same way as before or after hooks, using either a block or a symbol method name. The difference is that an around block or method accepts a single argument. Invoking the `call` method on that argument will continue invocation of the interaktor. For example, with a block:
|
|
@@ -285,6 +295,10 @@ class PlaceOrder
|
|
|
285
295
|
required(:order_params).filled(:hash)
|
|
286
296
|
end
|
|
287
297
|
|
|
298
|
+
success do
|
|
299
|
+
required(:order)
|
|
300
|
+
end
|
|
301
|
+
|
|
288
302
|
organize CreateOrder, ChargeCard, SendThankYou
|
|
289
303
|
end
|
|
290
304
|
```
|
|
@@ -312,7 +326,14 @@ class OrdersController < ApplicationController
|
|
|
312
326
|
end
|
|
313
327
|
```
|
|
314
328
|
|
|
315
|
-
The organizer passes
|
|
329
|
+
The organizer passes its own input arguments (if present) into first interaktor that it organizes, which is called and executed using those arguments. For the following interaktors in the organize list, each interaktor receives its input arguments from the previous interaktor (both input arguments and success arguments, with success arguments taking priority in the case of a name collision).
|
|
330
|
+
|
|
331
|
+
Any arguments which are _not_ accepted by the next interaktor (listed as required or optional input attributes) are dropped in the transition.
|
|
332
|
+
|
|
333
|
+
If the organizer specifies any success attributes, the final interaktor in the
|
|
334
|
+
organized list must also specify those success attributes. In general, it is
|
|
335
|
+
recommended to avoid using success attributes on an organizer in the first
|
|
336
|
+
place, to avoid coupling between the organizer and the interaktors it organizes.
|
|
316
337
|
|
|
317
338
|
#### Rollback
|
|
318
339
|
|
data/bin/_guard-core
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# This file was generated by Bundler.
|
|
6
|
+
#
|
|
7
|
+
# The application '_guard-core' is installed as part of a gem, and
|
|
8
|
+
# this file is here to facilitate running it.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
|
12
|
+
|
|
13
|
+
require "rubygems"
|
|
14
|
+
require "bundler/setup"
|
|
15
|
+
|
|
16
|
+
load Gem.bin_path("guard", "_guard-core")
|
data/bin/guard
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# This file was generated by Bundler.
|
|
6
|
+
#
|
|
7
|
+
# The application 'guard' is installed as part of a gem, and
|
|
8
|
+
# this file is here to facilitate running it.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
|
12
|
+
|
|
13
|
+
require "rubygems"
|
|
14
|
+
require "bundler/setup"
|
|
15
|
+
|
|
16
|
+
load Gem.bin_path("guard", "guard")
|
data/bin/rspec
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# This file was generated by Bundler.
|
|
6
|
+
#
|
|
7
|
+
# The application 'rspec' is installed as part of a gem, and
|
|
8
|
+
# this file is here to facilitate running it.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
|
12
|
+
|
|
13
|
+
require "rubygems"
|
|
14
|
+
require "bundler/setup"
|
|
15
|
+
|
|
16
|
+
load Gem.bin_path("rspec-core", "rspec")
|
data/bin/standardrb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# This file was generated by Bundler.
|
|
6
|
+
#
|
|
7
|
+
# The application 'standardrb' is installed as part of a gem, and
|
|
8
|
+
# this file is here to facilitate running it.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
|
12
|
+
|
|
13
|
+
require "rubygems"
|
|
14
|
+
require "bundler/setup"
|
|
15
|
+
|
|
16
|
+
load Gem.bin_path("standard", "standardrb")
|
data/interaktor.gemspec
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Gem::Specification.new do |spec|
|
|
2
2
|
spec.name = "interaktor"
|
|
3
|
-
spec.version = "0.
|
|
3
|
+
spec.version = "0.5.0"
|
|
4
4
|
|
|
5
5
|
spec.author = "Taylor Thurlow"
|
|
6
6
|
spec.email = "thurlow@hey.com"
|
|
@@ -9,12 +9,11 @@ Gem::Specification.new do |spec|
|
|
|
9
9
|
spec.homepage = "https://github.com/taylorthurlow/interaktor"
|
|
10
10
|
spec.license = "MIT"
|
|
11
11
|
spec.files = `git ls-files`.split
|
|
12
|
-
spec.
|
|
13
|
-
spec.required_ruby_version = ">= 2.5"
|
|
12
|
+
spec.required_ruby_version = ">= 3.0"
|
|
14
13
|
spec.require_path = "lib"
|
|
15
14
|
|
|
16
15
|
spec.add_runtime_dependency "dry-schema", "~> 1.0"
|
|
17
|
-
spec.add_runtime_dependency "zeitwerk", "
|
|
16
|
+
spec.add_runtime_dependency "zeitwerk", ">= 2"
|
|
18
17
|
|
|
19
18
|
spec.add_development_dependency "rake", "~> 13.0"
|
|
20
19
|
end
|
data/lib/interaktor/callable.rb
CHANGED
|
@@ -21,9 +21,10 @@ module Interaktor::Callable
|
|
|
21
21
|
#
|
|
22
22
|
# @return [Array<Symbol>]
|
|
23
23
|
def required_input_attributes
|
|
24
|
-
@required_input_attributes ||= input_schema
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
@required_input_attributes ||= input_schema
|
|
25
|
+
.info[:keys]
|
|
26
|
+
.select { |_, info| info[:required] }
|
|
27
|
+
.keys
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
# The list of attributes which are not required to be passed in when
|
|
@@ -41,14 +42,14 @@ module Interaktor::Callable
|
|
|
41
42
|
#
|
|
42
43
|
# See https://github.com/dry-rb/dry-schema/issues/347
|
|
43
44
|
@optional_input_attributes ||= begin
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
attributes_in_info = input_schema.info[:keys].keys
|
|
46
|
+
all_attributes = input_schema.key_map.keys.map(&:id)
|
|
47
|
+
optional_attributes_by_exclusion = all_attributes - attributes_in_info
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
explicitly_optional_attributes = input_schema.info[:keys].reject { |_, info| info[:required] }.keys
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
explicitly_optional_attributes + optional_attributes_by_exclusion
|
|
52
|
+
end
|
|
52
53
|
end
|
|
53
54
|
|
|
54
55
|
# The complete list of input attributes.
|
|
@@ -67,18 +68,14 @@ module Interaktor::Callable
|
|
|
67
68
|
@input_schema || Dry::Schema.Params
|
|
68
69
|
end
|
|
69
70
|
|
|
70
|
-
# @param
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def validate_input_schema(context)
|
|
74
|
-
return unless input_schema
|
|
75
|
-
|
|
76
|
-
result = input_schema.call(context)
|
|
71
|
+
# @param args [Hash]
|
|
72
|
+
def validate_input_schema(args)
|
|
73
|
+
return if !input_schema
|
|
77
74
|
|
|
78
|
-
if
|
|
75
|
+
if (errors = input_schema.call(args).errors).any?
|
|
79
76
|
raise Interaktor::Error::AttributeSchemaValidationError.new(
|
|
80
77
|
self,
|
|
81
|
-
|
|
78
|
+
errors.to_h
|
|
82
79
|
)
|
|
83
80
|
end
|
|
84
81
|
end
|
|
@@ -103,11 +100,11 @@ module Interaktor::Callable
|
|
|
103
100
|
attribute_name = key.id
|
|
104
101
|
|
|
105
102
|
# Define getter
|
|
106
|
-
define_method(attribute_name) { @
|
|
103
|
+
define_method(attribute_name) { @interaction.send(attribute_name) }
|
|
107
104
|
|
|
108
105
|
# Define setter
|
|
109
|
-
define_method("#{attribute_name}="
|
|
110
|
-
@
|
|
106
|
+
define_method(:"#{attribute_name}=") do |value|
|
|
107
|
+
@interaction.send(:"#{attribute_name}=", value)
|
|
111
108
|
end
|
|
112
109
|
end
|
|
113
110
|
end
|
|
@@ -141,14 +138,14 @@ module Interaktor::Callable
|
|
|
141
138
|
#
|
|
142
139
|
# See https://github.com/dry-rb/dry-schema/issues/347
|
|
143
140
|
@optional_failure_attributes ||= begin
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
141
|
+
attributes_in_info = failure_schema.info[:keys].keys
|
|
142
|
+
all_attributes = failure_schema.key_map.keys.map(&:id)
|
|
143
|
+
optional_attributes_by_exclusion = all_attributes - attributes_in_info
|
|
147
144
|
|
|
148
|
-
|
|
145
|
+
explicitly_optional_attributes = failure_schema.info[:keys].reject { |_, info| info[:required] }.keys
|
|
149
146
|
|
|
150
|
-
|
|
151
|
-
|
|
147
|
+
explicitly_optional_attributes + optional_attributes_by_exclusion
|
|
148
|
+
end
|
|
152
149
|
end
|
|
153
150
|
|
|
154
151
|
# The complete list of failure attributes.
|
|
@@ -167,18 +164,16 @@ module Interaktor::Callable
|
|
|
167
164
|
@failure_schema || Dry::Schema.Params
|
|
168
165
|
end
|
|
169
166
|
|
|
170
|
-
# @param
|
|
167
|
+
# @param args [Hash]
|
|
171
168
|
#
|
|
172
169
|
# @return [void]
|
|
173
|
-
def validate_failure_schema(
|
|
174
|
-
return
|
|
175
|
-
|
|
176
|
-
result = failure_schema.call(context)
|
|
170
|
+
def validate_failure_schema(args)
|
|
171
|
+
return if !failure_schema
|
|
177
172
|
|
|
178
|
-
if
|
|
173
|
+
if (errors = failure_schema.call(args).errors).any?
|
|
179
174
|
raise Interaktor::Error::AttributeSchemaValidationError.new(
|
|
180
175
|
self,
|
|
181
|
-
|
|
176
|
+
errors.to_h
|
|
182
177
|
)
|
|
183
178
|
end
|
|
184
179
|
end
|
|
@@ -209,8 +204,8 @@ module Interaktor::Callable
|
|
|
209
204
|
# @return [Array<Symbol>]
|
|
210
205
|
def required_success_attributes
|
|
211
206
|
@required_success_attributes ||= success_schema.info[:keys]
|
|
212
|
-
|
|
213
|
-
|
|
207
|
+
.select { |_, info| info[:required] }
|
|
208
|
+
.keys
|
|
214
209
|
end
|
|
215
210
|
|
|
216
211
|
# The list of attributes which are not required to be provided when failing
|
|
@@ -228,14 +223,14 @@ module Interaktor::Callable
|
|
|
228
223
|
#
|
|
229
224
|
# See https://github.com/dry-rb/dry-schema/issues/347
|
|
230
225
|
@optional_success_attributes ||= begin
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
226
|
+
attributes_in_info = success_schema.info[:keys].keys
|
|
227
|
+
all_attributes = success_schema.key_map.keys.map(&:id)
|
|
228
|
+
optional_attributes_by_exclusion = all_attributes - attributes_in_info
|
|
234
229
|
|
|
235
|
-
|
|
230
|
+
explicitly_optional_attributes = success_schema.info[:keys].reject { |_, info| info[:required] }.keys
|
|
236
231
|
|
|
237
|
-
|
|
238
|
-
|
|
232
|
+
explicitly_optional_attributes + optional_attributes_by_exclusion
|
|
233
|
+
end
|
|
239
234
|
end
|
|
240
235
|
|
|
241
236
|
# The complete list of success attributes.
|
|
@@ -254,18 +249,14 @@ module Interaktor::Callable
|
|
|
254
249
|
@success_schema || Dry::Schema.Params
|
|
255
250
|
end
|
|
256
251
|
|
|
257
|
-
# @param
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def validate_success_schema(context)
|
|
261
|
-
return unless success_schema
|
|
262
|
-
|
|
263
|
-
result = success_schema.call(context)
|
|
252
|
+
# @param args [Hash]
|
|
253
|
+
def validate_success_schema(args)
|
|
254
|
+
return if !success_schema
|
|
264
255
|
|
|
265
|
-
if
|
|
256
|
+
if (errors = success_schema.call(args).errors).any?
|
|
266
257
|
raise Interaktor::Error::AttributeSchemaValidationError.new(
|
|
267
258
|
self,
|
|
268
|
-
|
|
259
|
+
errors.to_h
|
|
269
260
|
)
|
|
270
261
|
end
|
|
271
262
|
end
|
|
@@ -289,25 +280,23 @@ module Interaktor::Callable
|
|
|
289
280
|
# Invoke an Interaktor. This is the primary public API method to an
|
|
290
281
|
# interaktor. Interaktor failures will not raise an exception.
|
|
291
282
|
#
|
|
292
|
-
# @param
|
|
293
|
-
# with attributes or an already-built context
|
|
283
|
+
# @param args [Hash, Interaktor::Interaction]
|
|
294
284
|
#
|
|
295
|
-
# @return [Interaktor::
|
|
296
|
-
def call(
|
|
297
|
-
execute(
|
|
285
|
+
# @return [Interaktor::Interaction]
|
|
286
|
+
def call(args = {})
|
|
287
|
+
execute(args, raise_exception: false)
|
|
298
288
|
end
|
|
299
289
|
|
|
300
290
|
# Invoke an Interaktor. This method behaves identically to `#call`, but if
|
|
301
|
-
# the interaktor
|
|
291
|
+
# the interaktor fails, `Interaktor::Failure` is raised.
|
|
302
292
|
#
|
|
303
|
-
# @param
|
|
304
|
-
# with attributes or an already-built context
|
|
293
|
+
# @param args [Hash, Interaktor::Interaction]
|
|
305
294
|
#
|
|
306
295
|
# @raises [Interaktor::Failure]
|
|
307
296
|
#
|
|
308
|
-
# @return [Interaktor::
|
|
309
|
-
def call!(
|
|
310
|
-
execute(
|
|
297
|
+
# @return [Interaktor::Interaction]
|
|
298
|
+
def call!(args = {})
|
|
299
|
+
execute(args, raise_exception: true)
|
|
311
300
|
end
|
|
312
301
|
|
|
313
302
|
private
|
|
@@ -315,31 +304,29 @@ module Interaktor::Callable
|
|
|
315
304
|
# The main execution method triggered by the public `#call` or `#call!`
|
|
316
305
|
# methods.
|
|
317
306
|
#
|
|
318
|
-
# @param
|
|
319
|
-
# with attributes or an already-built context
|
|
307
|
+
# @param args [Hash, Interaktor::Interaction]
|
|
320
308
|
# @param raise_exception [Boolean] whether or not to raise exception on
|
|
321
309
|
# failure
|
|
322
310
|
#
|
|
323
311
|
# @raises [Interaktor::Failure]
|
|
324
312
|
#
|
|
325
|
-
# @return [Interaktor::
|
|
326
|
-
def execute(
|
|
313
|
+
# @return [Interaktor::Interaction]
|
|
314
|
+
def execute(args, raise_exception:)
|
|
327
315
|
run_method = raise_exception ? :run! : :run
|
|
328
316
|
|
|
329
|
-
case
|
|
317
|
+
case args
|
|
330
318
|
when Hash
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
validate_input_schema(context)
|
|
319
|
+
if (disallowed_key = args.keys.find { |k| !input_attributes.include?(k.to_sym) })
|
|
320
|
+
raise Interaktor::Error::UnknownAttributeError.new(self, disallowed_key)
|
|
321
|
+
end
|
|
336
322
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
323
|
+
validate_input_schema(args)
|
|
324
|
+
new(args).tap(&run_method).instance_variable_get(:@interaction)
|
|
325
|
+
when Interaktor::Interaction
|
|
326
|
+
new(args).tap(&run_method).instance_variable_get(:@interaction)
|
|
340
327
|
else
|
|
341
328
|
raise ArgumentError,
|
|
342
|
-
|
|
329
|
+
"Expected a hash argument when calling the interaktor, got a #{args.class} instead."
|
|
343
330
|
end
|
|
344
331
|
end
|
|
345
332
|
end
|
|
@@ -11,7 +11,7 @@ class Interaktor::Error::OrganizerMissingPassedAttributeError < Interaktor::Erro
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def message
|
|
14
|
-
<<~MESSAGE.strip.tr("\n", "")
|
|
14
|
+
<<~MESSAGE.strip.tr("\n", " ")
|
|
15
15
|
An organized #{interaktor} interaktor requires a '#{attribute}' input
|
|
16
16
|
attribute, but none of the interaktors that come before it in the
|
|
17
17
|
organizer list it as a success attribute, and the organizer does not list
|
|
@@ -11,7 +11,7 @@ class Interaktor::Error::OrganizerSuccessAttributeMissingError < Interaktor::Err
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def message
|
|
14
|
-
<<~MESSAGE.strip.tr("\n", "")
|
|
14
|
+
<<~MESSAGE.strip.tr("\n", " ")
|
|
15
15
|
A #{interaktor} organizer requires a '#{attribute}' success attribute,
|
|
16
16
|
but none of the success attributes provided by any of the organized
|
|
17
17
|
interaktors list it.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class Interaktor::Error::UnknownAttributeError < Interaktor::Error::AttributeError
|
|
2
|
+
# @return [Symbol]
|
|
3
|
+
attr_reader :attribute
|
|
4
|
+
|
|
5
|
+
# @param interaktor [Class]
|
|
6
|
+
# @param attribute [Symbol]
|
|
7
|
+
def initialize(interaktor, attribute)
|
|
8
|
+
super(interaktor, [attribute])
|
|
9
|
+
|
|
10
|
+
@attribute = attribute
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def message
|
|
14
|
+
"Unknown attribute '#{attribute}'"
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/interaktor/failure.rb
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
# Error raised during
|
|
2
|
-
#
|
|
1
|
+
# Error raised during interaction failure. The error stores a copy of the failed
|
|
2
|
+
# interaction for debugging purposes.
|
|
3
3
|
class Interaktor::Failure < StandardError
|
|
4
|
-
# @return [Interaktor::
|
|
5
|
-
attr_reader :
|
|
4
|
+
# @return [Interaktor::Interaction] the context of this failure instance
|
|
5
|
+
attr_reader :interaction
|
|
6
6
|
|
|
7
|
-
# @param
|
|
8
|
-
# raised
|
|
9
|
-
def initialize(
|
|
10
|
-
@
|
|
7
|
+
# @param interaction [Interaktor::Interaction] the interaction in which the
|
|
8
|
+
# error was raised
|
|
9
|
+
def initialize(interaction = nil)
|
|
10
|
+
@interaction = interaction
|
|
11
11
|
super
|
|
12
12
|
end
|
|
13
13
|
end
|
data/lib/interaktor/hooks.rb
CHANGED
|
@@ -126,6 +126,38 @@ module Interaktor::Hooks
|
|
|
126
126
|
hooks.each { |hook| after_hooks.unshift(hook) }
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
+
# Public: Declare hooks to run after Interaktor invocation in an ensure block.
|
|
130
|
+
# The after method may be called multiple times; subsequent calls append
|
|
131
|
+
# declared hooks to existing ensure_hook hooks.
|
|
132
|
+
#
|
|
133
|
+
# hooks - Zero or more Symbol method names representing instance methods
|
|
134
|
+
# to be called after interaktor invocation.
|
|
135
|
+
# block - An optional block to be executed as a hook. If given, the block
|
|
136
|
+
# is executed before methods corresponding to any given Symbols.
|
|
137
|
+
#
|
|
138
|
+
# Examples
|
|
139
|
+
#
|
|
140
|
+
# class MyInteraktor
|
|
141
|
+
# include Interaktor
|
|
142
|
+
#
|
|
143
|
+
# ensure_hook :close_file
|
|
144
|
+
#
|
|
145
|
+
# ensure_hook do
|
|
146
|
+
# puts "finished"
|
|
147
|
+
# end
|
|
148
|
+
#
|
|
149
|
+
# def call
|
|
150
|
+
# puts "called"
|
|
151
|
+
# end
|
|
152
|
+
#
|
|
153
|
+
# private
|
|
154
|
+
#
|
|
155
|
+
# def close_file
|
|
156
|
+
# context.file.close
|
|
157
|
+
# end
|
|
158
|
+
# end
|
|
159
|
+
#
|
|
160
|
+
# Returns nothing.
|
|
129
161
|
def ensure_hook(*hooks, &block)
|
|
130
162
|
hooks << block if block
|
|
131
163
|
hooks.each { |hook| ensure_hooks.push(hook) }
|
|
@@ -188,6 +220,22 @@ module Interaktor::Hooks
|
|
|
188
220
|
@after_hooks ||= []
|
|
189
221
|
end
|
|
190
222
|
|
|
223
|
+
# Internal: An Array of declared hooks to run afer Interaktor
|
|
224
|
+
# invocation in an ensure block. The hooks appear in the order
|
|
225
|
+
# in which they will be run.
|
|
226
|
+
#
|
|
227
|
+
# Examples
|
|
228
|
+
#
|
|
229
|
+
# class MyInteraktor
|
|
230
|
+
# include Interaktor
|
|
231
|
+
#
|
|
232
|
+
# ensure_hook :set_finish_time, :say_goodbye
|
|
233
|
+
# end
|
|
234
|
+
#
|
|
235
|
+
# MyInteraktor.ensure_hooks
|
|
236
|
+
# # => [:say_goodbye, :set_finish_time]
|
|
237
|
+
#
|
|
238
|
+
# Returns an Array of Symbols and Procs.
|
|
191
239
|
def ensure_hooks
|
|
192
240
|
@ensure_hooks ||= []
|
|
193
241
|
end
|
|
@@ -195,8 +243,8 @@ module Interaktor::Hooks
|
|
|
195
243
|
|
|
196
244
|
private
|
|
197
245
|
|
|
198
|
-
# Internal: Run around, before and
|
|
199
|
-
# required block is surrounded with hooks and executed.
|
|
246
|
+
# Internal: Run around, before, after and ensure hooks around yielded execution.
|
|
247
|
+
# The required block is surrounded with hooks and executed.
|
|
200
248
|
#
|
|
201
249
|
# Examples
|
|
202
250
|
#
|
|
@@ -248,7 +296,9 @@ module Interaktor::Hooks
|
|
|
248
296
|
run_hooks(self.class.after_hooks)
|
|
249
297
|
end
|
|
250
298
|
|
|
251
|
-
|
|
299
|
+
# Internal: Run ensure hooks.
|
|
300
|
+
#
|
|
301
|
+
# Returns nothing.
|
|
252
302
|
def run_ensure_hooks
|
|
253
303
|
run_hooks(self.class.ensure_hooks)
|
|
254
304
|
end
|