teckel 0.2.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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