teckel 0.2.0 → 0.7.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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +111 -0
  3. data/LICENSE_LOGO +4 -0
  4. data/README.md +4 -4
  5. data/lib/teckel.rb +9 -4
  6. data/lib/teckel/chain.rb +31 -341
  7. data/lib/teckel/chain/config.rb +275 -0
  8. data/lib/teckel/chain/result.rb +38 -0
  9. data/lib/teckel/chain/runner.rb +62 -0
  10. data/lib/teckel/chain/step.rb +18 -0
  11. data/lib/teckel/config.rb +25 -28
  12. data/lib/teckel/contracts.rb +19 -0
  13. data/lib/teckel/operation.rb +84 -302
  14. data/lib/teckel/operation/config.rb +396 -0
  15. data/lib/teckel/operation/result.rb +92 -0
  16. data/lib/teckel/operation/runner.rb +74 -0
  17. data/lib/teckel/result.rb +52 -53
  18. data/lib/teckel/version.rb +1 -1
  19. data/spec/chain/around_hook_spec.rb +100 -0
  20. data/spec/chain/default_settings_spec.rb +39 -0
  21. data/spec/chain/inheritance_spec.rb +116 -0
  22. data/spec/chain/none_input_spec.rb +36 -0
  23. data/spec/chain/results_spec.rb +53 -0
  24. data/spec/chain_spec.rb +180 -0
  25. data/spec/config_spec.rb +26 -0
  26. data/spec/doctest_helper.rb +8 -0
  27. data/spec/operation/contract_trace_spec.rb +116 -0
  28. data/spec/operation/default_settings_spec.rb +94 -0
  29. data/spec/operation/fail_on_input_spec.rb +103 -0
  30. data/spec/operation/inheritance_spec.rb +94 -0
  31. data/spec/operation/result_spec.rb +55 -0
  32. data/spec/operation/results_spec.rb +117 -0
  33. data/spec/operation_spec.rb +483 -0
  34. data/spec/rb27/pattern_matching_spec.rb +193 -0
  35. data/spec/result_spec.rb +22 -0
  36. data/spec/spec_helper.rb +28 -0
  37. data/spec/support/dry_base.rb +8 -0
  38. data/spec/support/fake_db.rb +12 -0
  39. data/spec/support/fake_models.rb +20 -0
  40. data/spec/teckel_spec.rb +7 -0
  41. metadata +68 -28
  42. data/.codeclimate.yml +0 -3
  43. data/.github/workflows/ci.yml +0 -92
  44. data/.github/workflows/pages.yml +0 -50
  45. data/.gitignore +0 -15
  46. data/.rspec +0 -3
  47. data/.rubocop.yml +0 -12
  48. data/.ruby-version +0 -1
  49. data/DEVELOPMENT.md +0 -32
  50. data/Gemfile +0 -16
  51. data/Rakefile +0 -35
  52. data/bin/console +0 -15
  53. data/bin/rake +0 -29
  54. data/bin/rspec +0 -29
  55. data/bin/rubocop +0 -18
  56. data/bin/setup +0 -8
  57. data/lib/teckel/none.rb +0 -18
  58. data/lib/teckel/operation/results.rb +0 -72
  59. data/teckel.gemspec +0 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9fbb7828be6e84c5d609241e778e39e8509a4cede9f8960f097b31ef0d2473b
4
- data.tar.gz: b646f5954b1fb331186f52cf2a6ccd8521004bb26e59a09ec6b6d4d8575affa2
3
+ metadata.gz: df9c73577c9c93fbaaf2e7661a07bdc3b1b0397832a62a8edbe4eea846c4c029
4
+ data.tar.gz: 037041bf0eb6ae8bc41e354772d3ce1d04002a35ecccc0b1ecefa33a85845c3b
5
5
  SHA512:
6
- metadata.gz: 454b1869e2b2a8bf540f203fccf9962d628c236c2ced1411ded69ca2ccb1e0c361965c74ad73dea6c709b97b7757016c24487f983924111b03f4f0c1c2d0287e
7
- data.tar.gz: fd5f0e9e4e147238423454ef7a801139fd6f0ef3b1a7c2198285b7c471f1a26fb64c879647a9c041edb8129e4279a00e52572038959e9b8bd473668772dd78e2
6
+ metadata.gz: 2f6f601e816c972aed8e511b05fdbc418a48aab10c2ca246d108661cc25a8d514a41944b2e01b9294757a2a717696491f20f601f5f60e53faa45651aec48bc7f
7
+ data.tar.gz: 68bc46e5ed21cf5e639ee1fc00265b278f9062f42de9d179a07559862990a078df9dcb2ef26956eb629cf08e71409b9b05bd363a11930cb64ecdee533d63d2ea
@@ -1,5 +1,116 @@
1
1
  # Changes
2
2
 
3
+ ## 0.7.0
4
+
5
+ - Breaking: `Teckel::Chain` will not be required by default. require manually if needed `require "teckel/chain"` [GH-24]
6
+ - Breaking: Internally, `Teckel::Operation::Runner` instead of `:success` and `:failure` now only uses `:halt` as it's throw-catch symbol. [GH-26]
7
+ - Add: Using the default `Teckel::Operation::Runner`, `input_constructor` and `result_constructor` will be executed
8
+ within the context of the operation instance. This allows for `input_constructor` to call `fail!` and `success!`
9
+ without ever `call`ing the operation. [GH-26]
10
+
11
+
12
+ ## 0.6.0
13
+
14
+ - Breaking: Operations return values will be ignored. [GH-21]
15
+ * You'll need to use `success!` or `failure!`
16
+ * `success!` and `failure!` are now implemented on the `Runner`, which makes it easier to change their behavior (including the one above).
17
+
18
+ ## 0.5.0
19
+
20
+ - Fix: calling chain with settings and no input [GH-14]
21
+ - Add: Default settings for Operation and Chains [GH-17], [GH-18]
22
+ ```ruby
23
+ class MyOperation
24
+ include Teckel::Operation
25
+
26
+ settings Struct.new(:logger)
27
+
28
+ # If your settings class can cope with no input and you want to make sure
29
+ # `settings` gets initialized and set.
30
+ # settings will be #<struct logger=nil>
31
+ default_settings!
32
+
33
+ # settings will be #<struct logger=MyGlobalLogger>
34
+ default_settings!(MyGlobalLogger)
35
+
36
+ # settings will be #<struct logger=#<Logger:<...>>
37
+ default_settings! -> { settings.new(Logger.new("/tmp/my.log")) }
38
+ end
39
+
40
+ class Chain
41
+ include Teckel::Chain
42
+
43
+ # set or overwrite operation settings
44
+ default_settings!(a: MyOtherLogger)
45
+
46
+ step :a, MyOperation
47
+ end
48
+ ```
49
+
50
+ Internal:
51
+ - Move operation and chain config dsl methods into own module [GH-15]
52
+ - Code simplifications [GH-16]
53
+
54
+ ## 0.4.0
55
+
56
+ - Moving verbose examples from API docs into github pages
57
+ - `#finalize!` no longer freezes the entire Operation or Chain class, only it's settings. [GH-13]
58
+ - Add simple support for using Base classes. [GH-10]
59
+ Removes global configuration `Teckel::Config.default_constructor`
60
+ ```ruby
61
+ class ApplicationOperation
62
+ include Teckel::Operation
63
+ # you won't be able to overwrite any configuration in child classes,
64
+ # so take care which you want to declare
65
+ result!
66
+ settings Struct.new(:logger)
67
+ input_constructor :new
68
+ error Struct.new(:status, :messages)
69
+
70
+ def log(message)
71
+ return unless settings&.logger
72
+ logger << message
73
+ end
74
+ # you cannot call `finalize!` on partially declared Operations
75
+ end
76
+ ```
77
+ - Add support for setting your own Result objects. [GH-9]
78
+ - They should include and implement `Teckel::Result` which is needed by `Chain`.
79
+ - `Chain::StepFailure` got replaced with `Chain::Result`.
80
+ - the `Teckel::Operation::Results` module was removed. To let Operation use the default Result object, use the new helper `result!` instead.
81
+ - Add "settings"/dependency injection to Operation and Chains. [GH-7]
82
+ ```ruby
83
+ MyOperation.with(logger: STDOUT).call(params)
84
+
85
+ MyChain.with(some_step: { logger: STDOUT }).call(params)
86
+ ```
87
+ - [GH-5] Add support for ruby 2.7 pattern matching on Operation and Chain results. Both, array and hash notations are supported:
88
+ ```ruby
89
+ case MyOperation.call(params)
90
+ in [false, value]
91
+ # handle failure
92
+ in [true, value]
93
+ # handle success
94
+ end
95
+
96
+ case MyChain.call(params)
97
+ in { success: false, step: :foo, value: value }
98
+ # handle foo failure
99
+ in [success: false, step: :bar, value: value }
100
+ # handle bar failure
101
+ in { success: true, value: value }
102
+ # handle success
103
+ end
104
+ ```
105
+ - Fix setting a config twice to raise an error
106
+
107
+ ## 0.3.0
108
+
109
+ - `finalize!`'ing a Chain will also finalize all it's Operations
110
+ - Changed attribute naming of `StepFailure`:
111
+ + `.operation` will now give the operation class of the step - was `.step` before
112
+ + `.step` will now give the name of the step (which Operation failed) - was `.step_name` before
113
+
3
114
  ## 0.2.0
4
115
 
5
116
  - Around Hooks for Chains
@@ -0,0 +1,4 @@
1
+ The Teckel Logo artwork is NOT part of the Work as described in the LICENSE.
2
+ Any Derivative Works shall not use the Teckel Logo.
3
+
4
+ Copyright 2020 Jana Vogel <jana@dotless.de>
data/README.md CHANGED
@@ -34,11 +34,11 @@ Working with [Interactor](https://github.com/collectiveidea/interactor), [Trailb
34
34
 
35
35
  ## Usage
36
36
 
37
- For a full overview please see the Api Docs:
37
+ For a full overview please see the Docs:
38
38
 
39
- * [Operations](https://fnordfish.github.io/teckel/doc/Teckel/Operation.html)
40
- * [Operations with Result objects](https://fnordfish.github.io/teckel/doc/Teckel/Operation/Results.html)
41
- * [Chains](https://fnordfish.github.io/teckel/doc/Teckel/Chain.html)
39
+ * [Operations](https://fnordfish.github.io/teckel/operations/basics/)
40
+ * [Result Objects](https://fnordfish.github.io/teckel/operations/result_objects/)
41
+ * [Chains](https://fnordfish.github.io/teckel/chains/basics/)
42
42
 
43
43
 
44
44
  ```ruby
@@ -3,14 +3,19 @@
3
3
  require "teckel/version"
4
4
 
5
5
  module Teckel
6
+ # Base error class for this lib
6
7
  class Error < StandardError; end
8
+
9
+ # configuring the same value twice will raise this
7
10
  class FrozenConfigError < Teckel::Error; end
11
+
12
+ # missing important configurations (like contracts) will raise this
8
13
  class MissingConfigError < Teckel::Error; end
14
+
15
+ DEFAULT_CONSTRUCTOR = :[]
9
16
  end
10
17
 
11
18
  require_relative "teckel/config"
12
- require_relative "teckel/none"
13
- require_relative "teckel/operation"
19
+ require_relative "teckel/contracts"
14
20
  require_relative "teckel/result"
15
- require_relative "teckel/operation/results"
16
- require_relative "teckel/chain"
21
+ require_relative "teckel/operation"
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'forwardable'
3
+ require_relative 'chain/config'
4
+ require_relative 'chain/step'
5
+ require_relative 'chain/result'
6
+ require_relative 'chain/runner'
4
7
 
5
8
  module Teckel
6
9
  # Railway style execution of multiple Operations.
@@ -8,338 +11,49 @@ module Teckel
8
11
  # - Runs multiple Operations (steps) in order.
9
12
  # - The output of an earlier step is passed as input to the next step.
10
13
  # - Any failure will stop the execution chain (none of the later steps is called).
11
- # - All Operations (steps) must behave like
12
- # {Teckel::Operation::Results Teckel::Operation::Results} and return a result
13
- # object like {Teckel::Result}
14
- # - A failure response is wrapped into a {Teckel::Chain::StepFailure} giving
15
- # additional information about which step failed
14
+ # - All Operations (steps) must return a {Teckel::Result}
15
+ # - The result is wrapped into a {Teckel::Chain::Result}
16
16
  #
17
- # @see Teckel::Operation::Results
18
- #
19
- # @example Defining a simple Chain with three steps
20
- # class CreateUser
21
- # include ::Teckel::Operation::Results
22
- #
23
- # input Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer.optional)
24
- # output Types.Instance(User)
25
- # error Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
26
- #
27
- # def call(input)
28
- # user = User.new(name: input[:name], age: input[:age])
29
- # if user.save
30
- # success!(user)
31
- # else
32
- # fail!(message: "Could not save User", errors: user.errors)
33
- # end
34
- # end
35
- # end
36
- #
37
- # class LogUser
38
- # include ::Teckel::Operation::Results
39
- #
40
- # input Types.Instance(User)
41
- # output input
42
- #
43
- # def call(usr)
44
- # Logger.new(File::NULL).info("User #{usr.name} created")
45
- # usr # we need to return the correct output type
46
- # end
47
- # end
48
- #
49
- # class AddFriend
50
- # class << self
51
- # # Don't actually do this! It's not safe and for generating the failure sample only.
52
- # attr_accessor :fail_befriend
53
- # end
54
- #
55
- # include ::Teckel::Operation::Results
56
- #
57
- # input Types.Instance(User)
58
- # output Types::Hash.schema(user: Types.Instance(User), friend: Types.Instance(User))
59
- # error Types::Hash.schema(message: Types::String)
60
- #
61
- # def call(user)
62
- # if self.class.fail_befriend
63
- # fail!(message: "Did not find a friend.")
64
- # else
65
- # { user: user, friend: User.new(name: "A friend", age: 42) }
66
- # end
67
- # end
68
- # end
69
- #
70
- # class MyChain
71
- # include Teckel::Chain
72
- #
73
- # step :create, CreateUser
74
- # step :log, LogUser
75
- # step :befriend, AddFriend
76
- # end
77
- #
78
- # result = MyChain.call(name: "Bob", age: 23)
79
- # result.is_a?(Teckel::Result) #=> true
80
- # result.success[:user].is_a?(User) #=> true
81
- # result.success[:friend].is_a?(User) #=> true
82
- #
83
- # AddFriend.fail_befriend = true
84
- # failure_result = MyChain.call(name: "Bob", age: 23)
85
- # failure_result.is_a?(Teckel::Chain::StepFailure) #=> true
86
- #
87
- # # additional step information
88
- # failure_result.step_name #=> :befriend
89
- # failure_result.step #=> AddFriend
90
- #
91
- # # otherwise behaves just like a normal +Result+
92
- # failure_result.failure? #=> true
93
- # failure_result.failure #=> {message: "Did not find a friend."}
94
- #
95
- # @example DB transaction around hook
96
- # class CreateUser
97
- # include ::Teckel::Operation::Results
98
- #
99
- # input Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer.optional)
100
- # output Types.Instance(User)
101
- # error Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
102
- #
103
- # def call(input)
104
- # user = User.new(name: input[:name], age: input[:age])
105
- # if user.save
106
- # success!(user)
107
- # else
108
- # fail!(message: "Could not safe User", errors: user.errors)
109
- # end
110
- # end
111
- # end
112
- #
113
- # class AddFriend
114
- # class << self
115
- # # Don't actually do this! It's not safe and for generating the failure sample only.
116
- # attr_accessor :fail_befriend
117
- # end
118
- #
119
- # include ::Teckel::Operation::Results
120
- #
121
- # input Types.Instance(User)
122
- # output Types::Hash.schema(user: Types.Instance(User), friend: Types.Instance(User))
123
- # error Types::Hash.schema(message: Types::String)
124
- #
125
- # def call(user)
126
- # if self.class.fail_befriend
127
- # fail!(message: "Did not find a friend.")
128
- # else
129
- # { user: user, friend: User.new(name: "A friend", age: 42) }
130
- # end
131
- # end
132
- # end
133
- #
134
- # LOG = []
135
- #
136
- # class MyChain
137
- # include Teckel::Chain
138
- #
139
- # around ->(chain, input) {
140
- # result = nil
141
- # begin
142
- # LOG << :before
143
- #
144
- # FakeDB.transaction do
145
- # result = chain.call(input)
146
- # raise FakeDB::Rollback if result.failure?
147
- # end
148
- #
149
- # LOG << :after
150
- # result
151
- # rescue FakeDB::Rollback
152
- # LOG << :rollback
153
- # result
154
- # end
155
- # }
156
- #
157
- # step :create, CreateUser
158
- # step :befriend, AddFriend
159
- # end
160
- #
161
- # AddFriend.fail_befriend = true
162
- # failure_result = MyChain.call(name: "Bob", age: 23)
163
- # failure_result.is_a?(Teckel::Chain::StepFailure) #=> true
164
- #
165
- # # triggered DB rollback
166
- # LOG #=> [:before, :rollback]
167
- #
168
- # # additional step information
169
- # failure_result.step_name #=> :befriend
170
- # failure_result.step #=> AddFriend
171
- #
172
- # # otherwise behaves just like a normal +Result+
173
- # failure_result.failure? #=> true
174
- # failure_result.failure #=> {message: "Did not find a friend."}
17
+ # @see Teckel::Operation#result!
175
18
  module Chain
176
- # Like {Teckel::Result Teckel::Result} but for failing Chains
177
- #
178
- # When a Chain fails, it stores the failed +Operation+ and it's name.
179
- class StepFailure
180
- extend Forwardable
181
-
182
- def initialize(step, step_name, result)
183
- @step, @step_name, @result = step, step_name, result
184
- end
185
-
186
- # @!attribute step [R]
187
- # @return [Teckel::Operation] the failed Operation
188
- attr_reader :step
189
-
190
- # @!attribute step_name [R]
191
- # @return [String] the step name of the failed Operation
192
- attr_reader :step_name
193
-
194
- # @!attribute result [R]
195
- # @return [Teckel::Result] the failure Result
196
- attr_reader :result
197
-
198
- # @!method value
199
- # Delegates to +result.value+
200
- # @see Teckel::Result#value
201
- # @!method successful?
202
- # Delegates to +result.successful?+
203
- # @see Teckel::Result#successful?
204
- # @!method success
205
- # Delegates to +result.success+
206
- # @see Teckel::Result#success
207
- # @!method failure?
208
- # Delegates to +result.failure?+
209
- # @see Teckel::Result#failure?
210
- # @!method failure
211
- # Delegates to +result.failure+
212
- # @see Teckel::Result#failure
213
- def_delegators :@result, :value, :successful?, :success, :failure?, :failure
214
- end
215
-
216
- # The default implementation for executing a {Chain}
217
- #
218
- # @!visibility protected
219
- class Runner
220
- def initialize(steps)
221
- @steps = steps
222
- end
223
- attr_reader :steps
224
-
225
- # Run steps
226
- #
227
- # @param input Any form of input the first steps +input+ class can handle
228
- #
229
- # @return [Teckel::Result,Teckel::Chain::StepFailure] The result object wrapping
230
- # either the success or failure value. Note that the {StepFailure} behaves
231
- # just like a {Teckel::Result} with added information about which step failed.
232
- def call(input)
233
- last_result = input
234
- failed = nil
235
- steps.each do |(name, step)|
236
- last_result = step.call(last_result)
237
- if last_result.failure?
238
- failed = StepFailure.new(step, name, last_result)
239
- break
240
- end
241
- end
242
-
243
- failed || last_result
244
- end
245
- end
246
-
247
19
  module ClassMethods
248
20
  # The expected input for this chain
249
21
  # @return [Class] The {Teckel::Operation.input} of the first step
250
22
  def input
251
- @steps.first&.last&.input
23
+ steps.first&.operation&.input
252
24
  end
253
25
 
254
26
  # The expected output for this chain
255
27
  # @return [Class] The {Teckel::Operation.output} of the last step
256
28
  def output
257
- @steps.last&.last&.output
29
+ steps.last&.operation&.output
258
30
  end
259
31
 
260
32
  # List of all possible errors
261
33
  # @return [<Class>] List of all steps {Teckel::Operation.error}s
262
34
  def errors
263
- @steps.each_with_object([]) do |e, m|
264
- err = e.last&.error
35
+ steps.each_with_object([]) do |step, m|
36
+ err = step.operation.error
265
37
  m << err if err
266
38
  end
267
39
  end
268
40
 
269
- # Declare a {Operation} as a named step
270
- #
271
- # @param name [String,Symbol] The name of the operation.
272
- # This name is used in an error case to let you know which step failed.
273
- # @param operation [Operation::Results] The operation to call.
274
- # Must return a {Teckel::Result} object.
275
- def step(name, operation)
276
- @steps << [name, operation]
277
- end
278
-
279
- # Set or get the optional around hook.
280
- # A Hook might be given as a block or anything callable. The execution of
281
- # the chain is yielded to this hook. The first argument being the callable
282
- # chain ({Runner}) and the second argument the +input+ data. The hook also
283
- # needs to return the result.
284
- #
285
- # @param callable [Proc,{#call}] The hook to pass chain execution control to. (nil)
286
- #
287
- # @return [Proc,{#call}] The configured hook
288
- #
289
- # @example Around hook with block
290
- # OUTPUTS = []
291
- #
292
- # class Echo
293
- # include ::Teckel::Operation::Results
294
- #
295
- # input Hash
296
- # output input
297
- #
298
- # def call(hsh)
299
- # hsh
300
- # end
301
- # end
302
- #
303
- # class MyChain
304
- # include Teckel::Chain
305
- #
306
- # around do |chain, input|
307
- # OUTPUTS << "before start"
308
- # result = chain.call(input)
309
- # OUTPUTS << "after start"
310
- # result
311
- # end
312
- #
313
- # step :noop, Echo
314
- # end
315
- #
316
- # result = MyChain.call(some: 'test')
317
- # OUTPUTS #=> ["before start", "after start"]
318
- # result.success #=> { some: "test" }
319
- def around(callable = nil, &block)
320
- @config.for(:around, callable || block)
321
- end
322
-
323
- # @!attribute [r] runner()
324
- # @return [Class] The Runner class
325
- # @!visibility protected
326
-
327
- # Overwrite the default runner
328
- # @param klass [Class] A class like the {Runner}
329
- # @!visibility protected
330
- def runner(klass = nil)
331
- @config.for(:runner, klass) { Runner }
332
- end
333
-
334
41
  # The primary interface to call the chain with the given input.
335
42
  #
336
43
  # @param input Any form of input the first steps +input+ class can handle
337
44
  #
338
- # @return [Teckel::Result,Teckel::Chain::StepFailure] The result object wrapping
339
- # either the success or failure value. Note that the {StepFailure} behaves
340
- # just like a {Teckel::Result} with added information about which step failed.
341
- def call(input)
342
- runner = self.runner.new(@steps)
45
+ # @return [Teckel::Chain::Result] The result object wrapping
46
+ # the result value, the success state and last executed step.
47
+ def call(input = nil)
48
+ default_settings = self.default_settings
49
+
50
+ runner =
51
+ if default_settings
52
+ self.runner.new(self, default_settings)
53
+ else
54
+ self.runner.new(self)
55
+ end
56
+
343
57
  if around
344
58
  around.call(runner, input)
345
59
  else
@@ -347,45 +61,21 @@ module Teckel
347
61
  end
348
62
  end
349
63
 
350
- # Disallow any further changes to this Chain.
351
- #
352
- # @return [self] Frozen self
353
- # @!visibility public
354
- def finalize!
355
- freeze
356
- @steps.freeze
357
- @config.freeze
358
- self
359
- end
360
-
361
- # @!visibility public
362
- def dup
363
- super.tap do |copy|
364
- copy.instance_variable_set(:@steps, @steps.dup)
365
- copy.instance_variable_set(:@config, @config.dup)
366
- end
367
- end
368
-
369
- # @!visibility public
370
- def clone
371
- if frozen?
372
- super
64
+ # @param settings [Hash{String,Symbol => Object}] Set settings for a step by it's name
65
+ def with(settings)
66
+ runner = self.runner.new(self, settings)
67
+ if around
68
+ ->(input) { around.call(runner, input) }
373
69
  else
374
- super.tap do |copy|
375
- copy.instance_variable_set(:@steps, @steps.dup)
376
- copy.instance_variable_set(:@config, @config.dup)
377
- end
70
+ runner
378
71
  end
379
72
  end
73
+ alias :set :with
380
74
  end
381
75
 
382
76
  def self.included(receiver)
77
+ receiver.extend Config
383
78
  receiver.extend ClassMethods
384
-
385
- receiver.class_eval do
386
- @steps = []
387
- @config = Config.new
388
- end
389
79
  end
390
80
  end
391
81
  end