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