teckel 0.3.0 → 0.4.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/CHANGELOG.md +53 -0
- data/LICENSE_LOGO +4 -0
- data/README.md +4 -4
- data/lib/teckel.rb +9 -3
- data/lib/teckel/chain.rb +99 -271
- data/lib/teckel/chain/result.rb +38 -0
- data/lib/teckel/chain/runner.rb +51 -0
- data/lib/teckel/chain/step.rb +18 -0
- data/lib/teckel/config.rb +1 -23
- data/lib/teckel/contracts.rb +19 -0
- data/lib/teckel/operation.rb +309 -215
- data/lib/teckel/operation/result.rb +92 -0
- data/lib/teckel/operation/runner.rb +70 -0
- data/lib/teckel/result.rb +52 -53
- data/lib/teckel/version.rb +1 -1
- data/spec/chain/inheritance_spec.rb +116 -0
- data/spec/chain/results_spec.rb +53 -0
- data/spec/chain_around_hook_spec.rb +100 -0
- data/spec/chain_spec.rb +180 -0
- data/spec/config_spec.rb +26 -0
- data/spec/doctest_helper.rb +7 -0
- data/spec/operation/inheritance_spec.rb +94 -0
- data/spec/operation/result_spec.rb +34 -0
- data/spec/operation/results_spec.rb +117 -0
- data/spec/operation_spec.rb +485 -0
- data/spec/rb27/pattern_matching_spec.rb +193 -0
- data/spec/result_spec.rb +20 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/dry_base.rb +8 -0
- data/spec/support/fake_db.rb +12 -0
- data/spec/support/fake_models.rb +20 -0
- data/spec/teckel_spec.rb +7 -0
- metadata +52 -25
- data/.codeclimate.yml +0 -3
- data/.github/workflows/ci.yml +0 -92
- data/.github/workflows/pages.yml +0 -50
- data/.gitignore +0 -15
- data/.rspec +0 -3
- data/.rubocop.yml +0 -12
- data/.ruby-version +0 -1
- data/DEVELOPMENT.md +0 -32
- data/Gemfile +0 -16
- data/Rakefile +0 -35
- data/bin/console +0 -15
- data/bin/rake +0 -29
- data/bin/rspec +0 -29
- data/bin/rubocop +0 -18
- data/bin/setup +0 -8
- data/lib/teckel/none.rb +0 -18
- data/lib/teckel/operation/results.rb +0 -72
- data/teckel.gemspec +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 18179b7fd315a11bc51f0ac4cfbdba4c0e74ad82a6a7dc186289433906ec2783
|
4
|
+
data.tar.gz: 02eb5f5c59ac9983a92b457f9253a38f9188e3f2b17d4f30029aa545b3151181
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cbbbebfecccf6806da642dbc1dce843946c7dcaa27abb7c38467b0512ff5cb32d13b848fae0eaa0f8c4641f26e5b1f03f58f82fb70b3aa8aa1a891356698ff16
|
7
|
+
data.tar.gz: b312534680f31d7cff2a13326f3dc3cc01566110d3001b285d233c97548edbfb2fe008d59821ef0723b63687f12e95cca3f06f2f7c1a3f8d0e15d90caca4733d
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,58 @@
|
|
1
1
|
# Changes
|
2
2
|
|
3
|
+
## 0.4.0
|
4
|
+
|
5
|
+
- Moving verbose examples from API docs into github pages
|
6
|
+
- `#finalize!` no longer freezes the entire Operation or Chain class, only it's settings. [GH-13]
|
7
|
+
- Add simple support for using Base classes. [GH-10]
|
8
|
+
Removes global configuration `Teckel::Config.default_constructor`
|
9
|
+
```ruby
|
10
|
+
class ApplicationOperation
|
11
|
+
include Teckel::Operation
|
12
|
+
# you won't be able to overwrite any configuration in child classes,
|
13
|
+
# so take care which you want to declare
|
14
|
+
result!
|
15
|
+
settings Struct.new(:logger)
|
16
|
+
input_constructor :new
|
17
|
+
error Struct.new(:status, :messages)
|
18
|
+
|
19
|
+
def log(message)
|
20
|
+
return unless settings&.logger
|
21
|
+
logger << message
|
22
|
+
end
|
23
|
+
# you cannot call `finalize!` on partially declared Operations
|
24
|
+
end
|
25
|
+
```
|
26
|
+
- Add support for setting your own Result objects. [GH-9]
|
27
|
+
- They should include and implement `Teckel::Result` which is needed by `Chain`.
|
28
|
+
- `Chain::StepFailure` got replaced with `Chain::Result`.
|
29
|
+
- the `Teckel::Operation::Results` module was removed. To let Operation use the default Result object, use the new helper `result!` instead.
|
30
|
+
- Add "settings"/dependency injection to Operation and Chains. [GH-7]
|
31
|
+
```ruby
|
32
|
+
MyOperation.with(logger: STDOUT).call(params)
|
33
|
+
|
34
|
+
MyChain.with(some_step: { logger: STDOUT }).call(params)
|
35
|
+
```
|
36
|
+
- [GH-5] Add support for ruby 2.7 pattern matching on Operation and Chain results. Both, array and hash notations are supported:
|
37
|
+
```ruby
|
38
|
+
case MyOperation.call(params)
|
39
|
+
in [false, value]
|
40
|
+
# handle failure
|
41
|
+
in [true, value]
|
42
|
+
# handle success
|
43
|
+
end
|
44
|
+
|
45
|
+
case MyChain.call(params)
|
46
|
+
in { success: false, step: :foo, value: value }
|
47
|
+
# handle foo failure
|
48
|
+
in [success: false, step: :bar, value: value }
|
49
|
+
# handle bar failure
|
50
|
+
in { success: true, value: value }
|
51
|
+
# handle success
|
52
|
+
end
|
53
|
+
```
|
54
|
+
- Fix setting a config twice to raise an error
|
55
|
+
|
3
56
|
## 0.3.0
|
4
57
|
|
5
58
|
- `finalize!`'ing a Chain will also finalize all it's Operations
|
data/LICENSE_LOGO
ADDED
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
|
37
|
+
For a full overview please see the Docs:
|
38
38
|
|
39
|
-
* [Operations](https://fnordfish.github.io/teckel/
|
40
|
-
* [
|
41
|
-
* [Chains](https://fnordfish.github.io/teckel/
|
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
|
data/lib/teckel.rb
CHANGED
@@ -3,14 +3,20 @@
|
|
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/
|
13
|
-
require_relative "teckel/operation"
|
19
|
+
require_relative "teckel/contracts"
|
14
20
|
require_relative "teckel/result"
|
15
|
-
require_relative "teckel/operation
|
21
|
+
require_relative "teckel/operation"
|
16
22
|
require_relative "teckel/chain"
|
data/lib/teckel/chain.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative 'chain/step'
|
4
|
+
require_relative 'chain/result'
|
5
|
+
require_relative 'chain/runner'
|
4
6
|
|
5
7
|
module Teckel
|
6
8
|
# Railway style execution of multiple Operations.
|
@@ -8,253 +10,11 @@ module Teckel
|
|
8
10
|
# - Runs multiple Operations (steps) in order.
|
9
11
|
# - The output of an earlier step is passed as input to the next step.
|
10
12
|
# - Any failure will stop the execution chain (none of the later steps is called).
|
11
|
-
# - All Operations (steps) must
|
12
|
-
#
|
13
|
-
# object like {Teckel::Result}
|
14
|
-
# - A failure response is wrapped into a {Teckel::Chain::StepFailure} giving
|
15
|
-
# additional information about which step failed
|
13
|
+
# - All Operations (steps) must return a {Teckel::Result}
|
14
|
+
# - The result is wrapped into a {Teckel::Chain::Result}
|
16
15
|
#
|
17
|
-
# @see Teckel::Operation
|
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 #=> :befriend
|
89
|
-
# failure_result.operation #=> 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 #=> :befriend
|
170
|
-
# failure_result.operation #=> 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."}
|
16
|
+
# @see Teckel::Operation#result!
|
175
17
|
module Chain
|
176
|
-
# Internal wrapper of a step definition
|
177
|
-
Step = Struct.new(:name, :operation) do
|
178
|
-
def finalize!
|
179
|
-
name.freeze
|
180
|
-
operation.finalize!
|
181
|
-
freeze
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
# Like {Teckel::Result Teckel::Result} but for failing Chains
|
186
|
-
#
|
187
|
-
# When a Chain fails, it stores the failed +Operation+ and it's name.
|
188
|
-
class StepFailure
|
189
|
-
extend Forwardable
|
190
|
-
|
191
|
-
def initialize(step, result)
|
192
|
-
@step, @result = step, result
|
193
|
-
end
|
194
|
-
|
195
|
-
# @!method step
|
196
|
-
# Delegates to +step.name+
|
197
|
-
# @return [String,Symbol] The name of the failed operation.
|
198
|
-
def_delegator :@step, :name, :step
|
199
|
-
|
200
|
-
# @!method operation
|
201
|
-
# Delegates to +step.operation+
|
202
|
-
# @return [Teckel::Operation] The failed Operation class.
|
203
|
-
def_delegator :@step, :operation
|
204
|
-
|
205
|
-
# @!attribute result [R]
|
206
|
-
# @return [Teckel::Result] the failure Result
|
207
|
-
attr_reader :result
|
208
|
-
|
209
|
-
# @!method value
|
210
|
-
# Delegates to +result.value+
|
211
|
-
# @see Teckel::Result#value
|
212
|
-
# @!method successful?
|
213
|
-
# Delegates to +result.successful?+
|
214
|
-
# @see Teckel::Result#successful?
|
215
|
-
# @!method success
|
216
|
-
# Delegates to +result.success+
|
217
|
-
# @see Teckel::Result#success
|
218
|
-
# @!method failure?
|
219
|
-
# Delegates to +result.failure?+
|
220
|
-
# @see Teckel::Result#failure?
|
221
|
-
# @!method failure
|
222
|
-
# Delegates to +result.failure+
|
223
|
-
# @see Teckel::Result#failure
|
224
|
-
def_delegators :@result, :value, :successful?, :success, :failure?, :failure
|
225
|
-
end
|
226
|
-
|
227
|
-
# The default implementation for executing a {Chain}
|
228
|
-
#
|
229
|
-
# @!visibility protected
|
230
|
-
class Runner
|
231
|
-
def initialize(steps)
|
232
|
-
@steps = steps
|
233
|
-
end
|
234
|
-
attr_reader :steps
|
235
|
-
|
236
|
-
# Run steps
|
237
|
-
#
|
238
|
-
# @param input Any form of input the first steps +input+ class can handle
|
239
|
-
#
|
240
|
-
# @return [Teckel::Result,Teckel::Chain::StepFailure] The result object wrapping
|
241
|
-
# either the success or failure value. Note that the {StepFailure} behaves
|
242
|
-
# just like a {Teckel::Result} with added information about which step failed.
|
243
|
-
def call(input)
|
244
|
-
last_result = input
|
245
|
-
failed = nil
|
246
|
-
steps.each do |step|
|
247
|
-
last_result = step.operation.call(last_result)
|
248
|
-
if last_result.failure?
|
249
|
-
failed = StepFailure.new(step, last_result)
|
250
|
-
break
|
251
|
-
end
|
252
|
-
end
|
253
|
-
|
254
|
-
failed || last_result
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
18
|
module ClassMethods
|
259
19
|
# The expected input for this chain
|
260
20
|
# @return [Class] The {Teckel::Operation.input} of the first step
|
@@ -281,8 +41,8 @@ module Teckel
|
|
281
41
|
#
|
282
42
|
# @param name [String,Symbol] The name of the operation.
|
283
43
|
# This name is used in an error case to let you know which step failed.
|
284
|
-
# @param operation [Operation
|
285
|
-
#
|
44
|
+
# @param operation [Operation] The operation to call, which
|
45
|
+
# must return a {Teckel::Result} object.
|
286
46
|
def step(name, operation)
|
287
47
|
steps << Step.new(name, operation)
|
288
48
|
end
|
@@ -308,7 +68,8 @@ module Teckel
|
|
308
68
|
# OUTPUTS = []
|
309
69
|
#
|
310
70
|
# class Echo
|
311
|
-
# include ::Teckel::Operation
|
71
|
+
# include ::Teckel::Operation
|
72
|
+
# result!
|
312
73
|
#
|
313
74
|
# input Hash
|
314
75
|
# output input
|
@@ -349,15 +110,57 @@ module Teckel
|
|
349
110
|
@config.for(:runner, klass) { Runner }
|
350
111
|
end
|
351
112
|
|
113
|
+
# @overload result()
|
114
|
+
# Get the configured result object class wrapping {.error} or {.output}.
|
115
|
+
# @return [Class] The +result+ class, or {Teckel::Chain::Result} as default
|
116
|
+
#
|
117
|
+
# @overload result(klass)
|
118
|
+
# Set the result object class wrapping {.error} or {.output}.
|
119
|
+
# @param klass [Class] The +result+ class
|
120
|
+
# @return [Class] The +result+ class configured
|
121
|
+
def result(klass = nil)
|
122
|
+
@config.for(:result, klass) { const_defined?(:Result, false) ? self::Result : Teckel::Chain::Result }
|
123
|
+
end
|
124
|
+
|
125
|
+
# @overload result_constructor()
|
126
|
+
# The callable constructor to build an instance of the +result+ class.
|
127
|
+
# Defaults to {Teckel::DEFAULT_CONSTRUCTOR}
|
128
|
+
# @return [Proc] A callable that will return an instance of +result+ class.
|
129
|
+
#
|
130
|
+
# @overload result_constructor(sym_or_proc)
|
131
|
+
# Define how to build the +result+.
|
132
|
+
# @param sym_or_proc [Symbol, #call]
|
133
|
+
# - Either a +Symbol+ representing the _public_ method to call on the +result+ class.
|
134
|
+
# - Or anything that response to +#call+ (like a +Proc+).
|
135
|
+
# @return [#call] The callable constructor
|
136
|
+
#
|
137
|
+
# @example
|
138
|
+
# class MyOperation
|
139
|
+
# include Teckel::Operation
|
140
|
+
#
|
141
|
+
# class Result < Teckel::Operation::Result
|
142
|
+
# def initialize(value, success, step, options = {}); end
|
143
|
+
# end
|
144
|
+
#
|
145
|
+
# # If you need more control over how to build a new +Settings+ instance
|
146
|
+
# result_constructor ->(value, success, step) { result.new(value, success, step, {foo: :bar}) }
|
147
|
+
# end
|
148
|
+
def result_constructor(sym_or_proc = nil)
|
149
|
+
constructor = build_counstructor(result, sym_or_proc) unless sym_or_proc.nil?
|
150
|
+
|
151
|
+
@config.for(:result_constructor, constructor) {
|
152
|
+
build_counstructor(result, Teckel::DEFAULT_CONSTRUCTOR)
|
153
|
+
} || raise(MissingConfigError, "Missing result_constructor config for #{self}")
|
154
|
+
end
|
155
|
+
|
352
156
|
# The primary interface to call the chain with the given input.
|
353
157
|
#
|
354
158
|
# @param input Any form of input the first steps +input+ class can handle
|
355
159
|
#
|
356
|
-
# @return [Teckel::
|
357
|
-
#
|
358
|
-
|
359
|
-
|
360
|
-
runner = self.runner.new(steps)
|
160
|
+
# @return [Teckel::Chain::Result] The result object wrapping
|
161
|
+
# the result value, the success state and last executed step.
|
162
|
+
def call(input = nil)
|
163
|
+
runner = self.runner.new(self)
|
361
164
|
if around
|
362
165
|
around.call(runner, input)
|
363
166
|
else
|
@@ -365,12 +168,23 @@ module Teckel
|
|
365
168
|
end
|
366
169
|
end
|
367
170
|
|
171
|
+
# @param settings [Hash{String,Symbol => Object}] Set settings for a step by it's name
|
172
|
+
def with(settings)
|
173
|
+
runner = self.runner.new(self, settings)
|
174
|
+
if around
|
175
|
+
->(input) { around.call(runner, input) }
|
176
|
+
else
|
177
|
+
runner
|
178
|
+
end
|
179
|
+
end
|
180
|
+
alias :set :with
|
181
|
+
|
368
182
|
# @!visibility private
|
369
183
|
# @return [void]
|
370
184
|
def define!
|
371
185
|
raise MissingConfigError, "Cannot define Chain with no steps" if steps.empty?
|
372
186
|
|
373
|
-
%i[around runner].each { |e| public_send(e) }
|
187
|
+
%i[around runner result result_constructor].each { |e| public_send(e) }
|
374
188
|
steps.each(&:finalize!)
|
375
189
|
nil
|
376
190
|
end
|
@@ -384,7 +198,7 @@ module Teckel
|
|
384
198
|
define!
|
385
199
|
steps.freeze
|
386
200
|
@config.freeze
|
387
|
-
|
201
|
+
self
|
388
202
|
end
|
389
203
|
|
390
204
|
# Produces a shallow copy of this chain.
|
@@ -393,12 +207,7 @@ module Teckel
|
|
393
207
|
# @return [self]
|
394
208
|
# @!visibility public
|
395
209
|
def dup
|
396
|
-
super
|
397
|
-
new_config = @config.dup
|
398
|
-
new_config.replace(:steps) { steps.dup }
|
399
|
-
|
400
|
-
copy.instance_variable_set(:@config, new_config)
|
401
|
-
end
|
210
|
+
dup_config(super)
|
402
211
|
end
|
403
212
|
|
404
213
|
# Produces a clone of this chain.
|
@@ -410,22 +219,41 @@ module Teckel
|
|
410
219
|
if frozen?
|
411
220
|
super
|
412
221
|
else
|
413
|
-
super
|
414
|
-
|
415
|
-
|
222
|
+
dup_config(super)
|
223
|
+
end
|
224
|
+
end
|
416
225
|
|
417
|
-
|
418
|
-
|
226
|
+
# @!visibility private
|
227
|
+
def inherited(subclass)
|
228
|
+
dup_config(subclass)
|
229
|
+
end
|
230
|
+
|
231
|
+
# @!visibility private
|
232
|
+
def self.extended(base)
|
233
|
+
base.instance_variable_set(:@config, Config.new)
|
234
|
+
end
|
235
|
+
|
236
|
+
private
|
237
|
+
|
238
|
+
def dup_config(other_class)
|
239
|
+
new_config = @config.dup
|
240
|
+
new_config.replace(:steps) { steps.dup }
|
241
|
+
|
242
|
+
other_class.instance_variable_set(:@config, new_config)
|
243
|
+
other_class
|
244
|
+
end
|
245
|
+
|
246
|
+
def build_counstructor(on, sym_or_proc)
|
247
|
+
if sym_or_proc.is_a?(Symbol) && on.respond_to?(sym_or_proc)
|
248
|
+
on.public_method(sym_or_proc)
|
249
|
+
elsif sym_or_proc.respond_to?(:call)
|
250
|
+
sym_or_proc
|
419
251
|
end
|
420
252
|
end
|
421
253
|
end
|
422
254
|
|
423
255
|
def self.included(receiver)
|
424
256
|
receiver.extend ClassMethods
|
425
|
-
|
426
|
-
receiver.class_eval do
|
427
|
-
@config = Config.new
|
428
|
-
end
|
429
257
|
end
|
430
258
|
end
|
431
259
|
end
|