interaktor 0.4.0 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d187b41c956972596b434b05aa5e4b3f8558457953327e305c5d76165f7c195b
4
- data.tar.gz: e123fbeb3e8c1db843cb41b54023ca71d077a6947f0a3a50e0ec5bd78dfd4ddb
3
+ metadata.gz: c53ee54c903489b487c825865dd7e8c7d6ef7aaca737e62b656b48922484a972
4
+ data.tar.gz: f375cb18e249b2503ebadd167ffcd41e830e646a677944721c2b004d42c50deb
5
5
  SHA512:
6
- metadata.gz: 5549218310ebd0e3d0505eac4d43302f83c9508a0bee463f3a8c0f2042de5bfd25954aec67beae050a140606b14e75ce1e7ff8f0866726e5a24a09dd8071b4e6
7
- data.tar.gz: a7eabf0b930c9efd51ec3407e119234d6a1d29aa3d88494bacde287cc0e3d67ae9882707d8636ddba7289c6889f536bcdbad22eebc47a8344b659828a0718f4b
6
+ metadata.gz: 3f1fc6e18e8057443f2b38346051180970fc5b9d9ce98c9420e3943702c985ae2f2f2575139ce8fe3138063d975e23f29202c6d20a9722036b89b84fbff9647d
7
+ data.tar.gz: d25819e237ea94598f607aaa9d9f3365ee0ceea48a3ae75685c950191cbeb791858186a3f684b11ab4aa7f10e54f6a7419688b0a9250e0fc6267814fb4907e29
@@ -8,7 +8,7 @@ jobs:
8
8
  fail-fast: false
9
9
  matrix:
10
10
  os: [ubuntu-latest, macos-latest]
11
- ruby: [2.5, 2.6, 2.7, 3.0, head, debug]
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
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 "rubocop"
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
@@ -1,9 +1,8 @@
1
1
  # Interaktor
2
2
 
3
- [![Gem Version](https://img.shields.io/gem/v/interaktor.svg)](http://rubygems.org/gems/interaktor)
4
- [![Build Status](https://img.shields.io/travis/collectiveidea/interaktor/master.svg)](https://travis-ci.org/taylorthurlow/interaktor)
3
+ [![gem version](https://badge.fury.io/rb/interaktor.svg)](http://rubygems.org/gems/interaktor)
5
4
 
6
- **DISCLAIMER: Interaktor is under active development. Feel free to use it, but until 1.0 is released, any update could break compatibility with an older version.**
5
+ **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
6
 
8
7
  **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
8
 
@@ -66,7 +65,7 @@ CreateUser.call(name: "Foo Bar")
66
65
 
67
66
  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
67
 
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 be in a successful state.
68
+ 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
69
 
71
70
  ```ruby
72
71
  class CreateUser
@@ -108,7 +107,7 @@ end
108
107
 
109
108
  `#fail!` always throws an exception of type `Interaktor::Failure`.
110
109
 
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 swallows exceptions. When unit testing an interaktor, if calling custom business logic methods directly and bypassing `call`, be aware that `fail!` will generate such exceptions.
110
+ 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
111
 
113
112
  See _Interaktors in the controller_, below, for the recommended usage of `.call` and `#success?`.
114
113
 
@@ -144,6 +143,16 @@ after do
144
143
  end
145
144
  ```
146
145
 
146
+ #### Ensure hooks
147
+
148
+ Very similar to `after` hooks, but the hooks are run in an `ensure` block in the order they are defined.
149
+
150
+ ```ruby
151
+ ensure_hook do
152
+ file.close
153
+ end
154
+ ```
155
+
147
156
  #### Around hooks
148
157
 
149
158
  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 +294,10 @@ class PlaceOrder
285
294
  required(:order_params).filled(:hash)
286
295
  end
287
296
 
297
+ success do
298
+ required(:order)
299
+ end
300
+
288
301
  organize CreateOrder, ChargeCard, SendThankYou
289
302
  end
290
303
  ```
@@ -312,7 +325,14 @@ class OrdersController < ApplicationController
312
325
  end
313
326
  ```
314
327
 
315
- The organizer passes any of its own defined attributes into first interaktor that it organizes. That first interaktor is then called and executed using those attributes. For the following interaktors in the organize list, each interaktor receives its attributes from the previous interaktor (both input attributes and success attributes). Any attributes which are _not_ accepted by the next interaktor (listed as required or optional attributes) are dropped in the transition.
328
+ 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).
329
+
330
+ Any arguments which are _not_ accepted by the next interaktor (listed as required or optional input attributes) are dropped in the transition.
331
+
332
+ If the organizer specifies any success attributes, the final interaktor in the
333
+ organized list must also specify those success attributes. In general, it is
334
+ recommended to avoid using success attributes on an organizer in the first
335
+ place, to avoid coupling between the organizer and the interaktors it organizes.
316
336
 
317
337
  #### Rollback
318
338
 
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.4.0"
3
+ spec.version = "0.5.1"
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.test_files = spec.files.grep(/^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", "~> 2.0"
16
+ spec.add_runtime_dependency "zeitwerk", ">= 2"
18
17
 
19
18
  spec.add_development_dependency "rake", "~> 13.0"
20
19
  end
@@ -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.info[:keys]
25
- .select { |_, info| info[:required] }
26
- .keys
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
- attributes_in_info = input_schema.info[:keys].keys
45
- all_attributes = input_schema.key_map.keys.map(&:id)
46
- optional_attributes_by_exclusion = all_attributes - attributes_in_info
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
- explicitly_optional_attributes = input_schema.info[:keys].reject { |_, info| info[:required] }.keys
49
+ explicitly_optional_attributes = input_schema.info[:keys].reject { |_, info| info[:required] }.keys
49
50
 
50
- explicitly_optional_attributes + optional_attributes_by_exclusion
51
- end
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 context [Hash]
71
- #
72
- # @return [void]
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 result.errors.any?
75
+ if (errors = input_schema.call(args).errors).any?
79
76
  raise Interaktor::Error::AttributeSchemaValidationError.new(
80
77
  self,
81
- result.errors.to_h,
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) { @context.send(attribute_name) }
103
+ define_method(attribute_name) { @interaction.send(attribute_name) }
107
104
 
108
105
  # Define setter
109
- define_method("#{attribute_name}=".to_sym) do |value|
110
- @context.send("#{attribute_name}=".to_sym, value)
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
- attributes_in_info = failure_schema.info[:keys].keys
145
- all_attributes = failure_schema.key_map.keys.map(&:id)
146
- optional_attributes_by_exclusion = all_attributes - attributes_in_info
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
- explicitly_optional_attributes = failure_schema.info[:keys].reject { |_, info| info[:required] }.keys
145
+ explicitly_optional_attributes = failure_schema.info[:keys].reject { |_, info| info[:required] }.keys
149
146
 
150
- explicitly_optional_attributes + optional_attributes_by_exclusion
151
- end
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 context [Hash]
167
+ # @param args [Hash]
171
168
  #
172
169
  # @return [void]
173
- def validate_failure_schema(context)
174
- return unless failure_schema
175
-
176
- result = failure_schema.call(context)
170
+ def validate_failure_schema(args)
171
+ return if !failure_schema
177
172
 
178
- if result.errors.any?
173
+ if (errors = failure_schema.call(args).errors).any?
179
174
  raise Interaktor::Error::AttributeSchemaValidationError.new(
180
175
  self,
181
- result.errors.to_h,
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
- .select { |_, info| info[:required] }
213
- .keys
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
- attributes_in_info = success_schema.info[:keys].keys
232
- all_attributes = success_schema.key_map.keys.map(&:id)
233
- optional_attributes_by_exclusion = all_attributes - attributes_in_info
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
- explicitly_optional_attributes = success_schema.info[:keys].reject { |_, info| info[:required] }.keys
230
+ explicitly_optional_attributes = success_schema.info[:keys].reject { |_, info| info[:required] }.keys
236
231
 
237
- explicitly_optional_attributes + optional_attributes_by_exclusion
238
- end
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 context [Hash]
258
- #
259
- # @return [void]
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 result.errors.any?
256
+ if (errors = success_schema.call(args).errors).any?
266
257
  raise Interaktor::Error::AttributeSchemaValidationError.new(
267
258
  self,
268
- result.errors.to_h,
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 context [Hash, Interaktor::Context] the context object as a hash
293
- # with attributes or an already-built context
283
+ # @param args [Hash, Interaktor::Interaction]
294
284
  #
295
- # @return [Interaktor::Context] the context, following interaktor execution
296
- def call(context = {})
297
- execute(context, false)
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 is failed, `Interaktor::Failure` is raised.
291
+ # the interaktor fails, `Interaktor::Failure` is raised.
302
292
  #
303
- # @param context [Hash, Interaktor::Context] the context object as a hash
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::Context] the context, following interaktor execution
309
- def call!(context = {})
310
- execute(context, true)
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 context [Hash, Interaktor::Context] the context object as a hash
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::Context] the context, following interaktor execution
326
- def execute(context, raise_exception)
313
+ # @return [Interaktor::Interaction]
314
+ def execute(args, raise_exception:)
327
315
  run_method = raise_exception ? :run! : :run
328
316
 
329
- case context
317
+ case args
330
318
  when Hash
331
- # Silently remove any attributes that are not included in the schema
332
- allowed_keys = input_schema.key_map.keys.map { |k| k.name.to_sym }
333
- context.select! { |k, _| allowed_keys.include?(k.to_sym) }
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
- new(context).tap(&run_method).instance_variable_get(:@context)
338
- when Interaktor::Context
339
- new(context).tap(&run_method).instance_variable_get(:@context)
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
- "Expected a hash argument when calling the interaktor, got a #{context.class} instead."
329
+ "Expected a hash argument when calling the interaktor, got a #{args.class} instead."
343
330
  end
344
331
  end
345
332
  end
@@ -0,0 +1,10 @@
1
+ class Interaktor::Error::InvalidMethodForStateError < Interaktor::Error::Base
2
+ attr_reader :message
3
+
4
+ # @param interaktor [Class]
5
+ # @param method [Symbol]
6
+ def initialize(interaktor, message)
7
+ super(interaktor)
8
+ @message = message
9
+ end
10
+ 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
@@ -1,13 +1,13 @@
1
- # Error raised during Interaktor::Context failure. The error stores a copy of
2
- # the failed context for debugging purposes.
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::Context] the context of this failure instance
5
- attr_reader :context
4
+ # @return [Interaktor::Interaction] the context of this failure instance
5
+ attr_reader :interaction
6
6
 
7
- # @param context [Interaktor::Context] the context in which the error was
8
- # raised
9
- def initialize(context = nil)
10
- @context = context
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
@@ -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 after hooks around yielded execution. The
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