teckel 0.4.0 → 0.8.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 +71 -0
- data/README.md +3 -3
- data/lib/teckel/chain/config.rb +286 -0
- data/lib/teckel/chain/result.rb +3 -3
- data/lib/teckel/chain/runner.rb +28 -17
- data/lib/teckel/chain.rb +14 -190
- data/lib/teckel/config.rb +13 -7
- data/lib/teckel/operation/config.rb +400 -0
- data/lib/teckel/operation/result.rb +8 -8
- data/lib/teckel/operation/runner.rb +29 -25
- data/lib/teckel/operation.rb +87 -388
- data/lib/teckel/result.rb +8 -6
- data/lib/teckel/version.rb +1 -1
- data/lib/teckel.rb +0 -1
- data/spec/chain/around_hook_spec.rb +100 -0
- data/spec/chain/default_settings_spec.rb +39 -0
- data/spec/chain/inheritance_spec.rb +44 -44
- data/spec/chain/none_input_spec.rb +36 -0
- data/spec/chain/results_spec.rb +28 -28
- data/spec/chain_spec.rb +132 -57
- data/spec/doctest_helper.rb +1 -0
- data/spec/operation/config_spec.rb +227 -0
- data/spec/operation/contract_trace_spec.rb +118 -0
- data/spec/operation/default_settings_spec.rb +120 -0
- data/spec/operation/fail_on_input_spec.rb +103 -0
- data/spec/operation/inheritance_spec.rb +38 -38
- data/spec/operation/result_spec.rb +27 -12
- data/spec/operation/results_spec.rb +67 -67
- data/spec/operation_spec.rb +276 -230
- data/spec/rb27/pattern_matching_spec.rb +2 -2
- data/spec/result_spec.rb +12 -10
- data/spec/spec_helper.rb +10 -0
- metadata +41 -12
- data/spec/chain_around_hook_spec.rb +0 -100
data/lib/teckel/chain.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'chain/config'
|
3
4
|
require_relative 'chain/step'
|
4
5
|
require_relative 'chain/result'
|
5
6
|
require_relative 'chain/runner'
|
@@ -37,122 +38,6 @@ module Teckel
|
|
37
38
|
end
|
38
39
|
end
|
39
40
|
|
40
|
-
# Declare a {Operation} as a named step
|
41
|
-
#
|
42
|
-
# @param name [String,Symbol] The name of the operation.
|
43
|
-
# This name is used in an error case to let you know which step failed.
|
44
|
-
# @param operation [Operation] The operation to call, which
|
45
|
-
# must return a {Teckel::Result} object.
|
46
|
-
def step(name, operation)
|
47
|
-
steps << Step.new(name, operation)
|
48
|
-
end
|
49
|
-
|
50
|
-
# Get the list of defined steps
|
51
|
-
#
|
52
|
-
# @return [<Step>]
|
53
|
-
def steps
|
54
|
-
@config.for(:steps) { [] }
|
55
|
-
end
|
56
|
-
|
57
|
-
# Set or get the optional around hook.
|
58
|
-
# A Hook might be given as a block or anything callable. The execution of
|
59
|
-
# the chain is yielded to this hook. The first argument being the callable
|
60
|
-
# chain ({Runner}) and the second argument the +input+ data. The hook also
|
61
|
-
# needs to return the result.
|
62
|
-
#
|
63
|
-
# @param callable [Proc,{#call}] The hook to pass chain execution control to. (nil)
|
64
|
-
#
|
65
|
-
# @return [Proc,{#call}] The configured hook
|
66
|
-
#
|
67
|
-
# @example Around hook with block
|
68
|
-
# OUTPUTS = []
|
69
|
-
#
|
70
|
-
# class Echo
|
71
|
-
# include ::Teckel::Operation
|
72
|
-
# result!
|
73
|
-
#
|
74
|
-
# input Hash
|
75
|
-
# output input
|
76
|
-
#
|
77
|
-
# def call(hsh)
|
78
|
-
# hsh
|
79
|
-
# end
|
80
|
-
# end
|
81
|
-
#
|
82
|
-
# class MyChain
|
83
|
-
# include Teckel::Chain
|
84
|
-
#
|
85
|
-
# around do |chain, input|
|
86
|
-
# OUTPUTS << "before start"
|
87
|
-
# result = chain.call(input)
|
88
|
-
# OUTPUTS << "after start"
|
89
|
-
# result
|
90
|
-
# end
|
91
|
-
#
|
92
|
-
# step :noop, Echo
|
93
|
-
# end
|
94
|
-
#
|
95
|
-
# result = MyChain.call(some: 'test')
|
96
|
-
# OUTPUTS #=> ["before start", "after start"]
|
97
|
-
# result.success #=> { some: "test" }
|
98
|
-
def around(callable = nil, &block)
|
99
|
-
@config.for(:around, callable || block)
|
100
|
-
end
|
101
|
-
|
102
|
-
# @!attribute [r] runner()
|
103
|
-
# @return [Class] The Runner class
|
104
|
-
# @!visibility protected
|
105
|
-
|
106
|
-
# Overwrite the default runner
|
107
|
-
# @param klass [Class] A class like the {Runner}
|
108
|
-
# @!visibility protected
|
109
|
-
def runner(klass = nil)
|
110
|
-
@config.for(:runner, klass) { Runner }
|
111
|
-
end
|
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
|
-
|
156
41
|
# The primary interface to call the chain with the given input.
|
157
42
|
#
|
158
43
|
# @param input Any form of input the first steps +input+ class can handle
|
@@ -160,7 +45,15 @@ module Teckel
|
|
160
45
|
# @return [Teckel::Chain::Result] The result object wrapping
|
161
46
|
# the result value, the success state and last executed step.
|
162
47
|
def call(input = nil)
|
163
|
-
|
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
|
+
|
164
57
|
if around
|
165
58
|
around.call(runner, input)
|
166
59
|
else
|
@@ -178,82 +71,13 @@ module Teckel
|
|
178
71
|
end
|
179
72
|
end
|
180
73
|
alias :set :with
|
181
|
-
|
182
|
-
# @!visibility private
|
183
|
-
# @return [void]
|
184
|
-
def define!
|
185
|
-
raise MissingConfigError, "Cannot define Chain with no steps" if steps.empty?
|
186
|
-
|
187
|
-
%i[around runner result result_constructor].each { |e| public_send(e) }
|
188
|
-
steps.each(&:finalize!)
|
189
|
-
nil
|
190
|
-
end
|
191
|
-
|
192
|
-
# Disallow any further changes to this Chain.
|
193
|
-
# @note This also calls +finalize!+ on all Operations defined as steps.
|
194
|
-
#
|
195
|
-
# @return [self] Frozen self
|
196
|
-
# @!visibility public
|
197
|
-
def finalize!
|
198
|
-
define!
|
199
|
-
steps.freeze
|
200
|
-
@config.freeze
|
201
|
-
self
|
202
|
-
end
|
203
|
-
|
204
|
-
# Produces a shallow copy of this chain.
|
205
|
-
# It's {around}, {runner} and {steps} will get +dup+'ed
|
206
|
-
#
|
207
|
-
# @return [self]
|
208
|
-
# @!visibility public
|
209
|
-
def dup
|
210
|
-
dup_config(super)
|
211
|
-
end
|
212
|
-
|
213
|
-
# Produces a clone of this chain.
|
214
|
-
# It's {around}, {runner} and {steps} will get +dup+'ed
|
215
|
-
#
|
216
|
-
# @return [self]
|
217
|
-
# @!visibility public
|
218
|
-
def clone
|
219
|
-
if frozen?
|
220
|
-
super
|
221
|
-
else
|
222
|
-
dup_config(super)
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
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
|
251
|
-
end
|
252
|
-
end
|
253
74
|
end
|
254
75
|
|
255
76
|
def self.included(receiver)
|
256
|
-
receiver.
|
77
|
+
receiver.class_eval do
|
78
|
+
extend Config
|
79
|
+
extend ClassMethods
|
80
|
+
end
|
257
81
|
end
|
258
82
|
end
|
259
83
|
end
|
data/lib/teckel/config.rb
CHANGED
@@ -19,11 +19,7 @@ module Teckel
|
|
19
19
|
# @!visibility private
|
20
20
|
def for(key, value = nil, &block)
|
21
21
|
if value.nil?
|
22
|
-
|
23
|
-
@config[key] ||= @config.fetch(key, &block)
|
24
|
-
else
|
25
|
-
@config[key]
|
26
|
-
end
|
22
|
+
get_or_set(key, &block)
|
27
23
|
elsif @config.key?(key)
|
28
24
|
raise FrozenConfigError, "Configuration #{key} is already set"
|
29
25
|
else
|
@@ -39,14 +35,24 @@ module Teckel
|
|
39
35
|
# @!visibility private
|
40
36
|
def freeze
|
41
37
|
@config.freeze
|
42
|
-
super
|
38
|
+
super()
|
43
39
|
end
|
44
40
|
|
45
41
|
# @!visibility private
|
46
42
|
def dup
|
47
|
-
super.tap do |copy|
|
43
|
+
super().tap do |copy|
|
48
44
|
copy.instance_variable_set(:@config, @config.dup)
|
49
45
|
end
|
50
46
|
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def get_or_set(key, &block)
|
51
|
+
if block
|
52
|
+
@config[key] ||= @config.fetch(key, &block)
|
53
|
+
else
|
54
|
+
@config[key]
|
55
|
+
end
|
56
|
+
end
|
51
57
|
end
|
52
58
|
end
|
@@ -0,0 +1,400 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Teckel
|
4
|
+
module Operation
|
5
|
+
module Config
|
6
|
+
# @overload input()
|
7
|
+
# Get the configured class wrapping the input data structure.
|
8
|
+
# @return [Class] The +input+ class
|
9
|
+
# @overload input(klass)
|
10
|
+
# Set the class wrapping the input data structure.
|
11
|
+
# @param klass [Class] The +input+ class
|
12
|
+
# @return [Class] The +input+ class
|
13
|
+
def input(klass = nil)
|
14
|
+
@config.for(:input, klass) { self::Input if const_defined?(:Input) } ||
|
15
|
+
raise(MissingConfigError, "Missing input config for #{self}")
|
16
|
+
end
|
17
|
+
|
18
|
+
# @overload input_constructor()
|
19
|
+
# The callable constructor to build an instance of the +input+ class.
|
20
|
+
# Defaults to {Teckel::DEFAULT_CONSTRUCTOR}
|
21
|
+
# @return [Proc] A callable that will return an instance of the +input+ class.
|
22
|
+
#
|
23
|
+
# @overload input_constructor(sym_or_proc)
|
24
|
+
# Define how to build the +input+.
|
25
|
+
# @param sym_or_proc [Symbol, #call]
|
26
|
+
# - Either a +Symbol+ representing the _public_ method to call on the +input+ class.
|
27
|
+
# - Or anything that response to +#call+ (like a +Proc+).
|
28
|
+
# @return [#call] The callable constructor
|
29
|
+
#
|
30
|
+
# @example simple symbol to method constructor
|
31
|
+
# class MyOperation
|
32
|
+
# include Teckel::Operation
|
33
|
+
#
|
34
|
+
# class Input
|
35
|
+
# def initialize(name:, age:); end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# # If you need more control over how to build a new +Input+ instance
|
39
|
+
# # MyOperation.call(name: "Bob", age: 23) # -> Input.new(name: "Bob", age: 23)
|
40
|
+
# input_constructor :new
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# MyOperation.input_constructor.is_a?(Method) #=> true
|
44
|
+
#
|
45
|
+
# @example Custom Proc constructor
|
46
|
+
# class MyOperation
|
47
|
+
# include Teckel::Operation
|
48
|
+
#
|
49
|
+
# class Input
|
50
|
+
# def initialize(*args, **opts); end
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# # If you need more control over how to build a new +Input+ instance
|
54
|
+
# # MyOperation.call("foo", opt: "bar") # -> Input.new(name: "foo", opt: "bar")
|
55
|
+
# input_constructor ->(name, options) { Input.new(name: name, **options) }
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# MyOperation.input_constructor.is_a?(Proc) #=> true
|
59
|
+
def input_constructor(sym_or_proc = nil)
|
60
|
+
get_set_constructor(:input_constructor, input, sym_or_proc)
|
61
|
+
end
|
62
|
+
|
63
|
+
# @overload output()
|
64
|
+
# Get the configured class wrapping the output data structure.
|
65
|
+
# Defaults to {Teckel::DEFAULT_CONSTRUCTOR}
|
66
|
+
# @return [Class] The +output+ class
|
67
|
+
#
|
68
|
+
# @overload output(klass)
|
69
|
+
# Set the class wrapping the output data structure.
|
70
|
+
# @param klass [Class] The +output+ class
|
71
|
+
# @return [Class] The +output+ class
|
72
|
+
def output(klass = nil)
|
73
|
+
@config.for(:output, klass) { self::Output if const_defined?(:Output) } ||
|
74
|
+
raise(MissingConfigError, "Missing output config for #{self}")
|
75
|
+
end
|
76
|
+
|
77
|
+
# @overload output_constructor()
|
78
|
+
# The callable constructor to build an instance of the +output+ class.
|
79
|
+
# Defaults to {Teckel::DEFAULT_CONSTRUCTOR}
|
80
|
+
# @return [Proc] A callable that will return an instance of +output+ class.
|
81
|
+
#
|
82
|
+
# @overload output_constructor(sym_or_proc)
|
83
|
+
# Define how to build the +output+.
|
84
|
+
# @param sym_or_proc [Symbol, #call]
|
85
|
+
# - Either a +Symbol+ representing the _public_ method to call on the +output+ class.
|
86
|
+
# - Or anything that response to +#call+ (like a +Proc+).
|
87
|
+
# @return [#call] The callable constructor
|
88
|
+
#
|
89
|
+
# @example
|
90
|
+
# class MyOperation
|
91
|
+
# include Teckel::Operation
|
92
|
+
#
|
93
|
+
# class Output
|
94
|
+
# def initialize(*args, **opts); end
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# # MyOperation.call("foo", "bar") # -> Output.new("foo", "bar")
|
98
|
+
# output_constructor :new
|
99
|
+
#
|
100
|
+
# # If you need more control over how to build a new +Output+ instance
|
101
|
+
# # MyOperation.call("foo", opt: "bar") # -> Output.new(name: "foo", opt: "bar")
|
102
|
+
# output_constructor ->(name, options) { Output.new(name: name, **options) }
|
103
|
+
# end
|
104
|
+
def output_constructor(sym_or_proc = nil)
|
105
|
+
get_set_constructor(:output_constructor, output, sym_or_proc)
|
106
|
+
end
|
107
|
+
|
108
|
+
# @overload error()
|
109
|
+
# Get the configured class wrapping the error data structure.
|
110
|
+
# @return [Class] The +error+ class
|
111
|
+
#
|
112
|
+
# @overload error(klass)
|
113
|
+
# Set the class wrapping the error data structure.
|
114
|
+
# @param klass [Class] The +error+ class
|
115
|
+
# @return [Class,nil] The +error+ class or +nil+ if it does not error
|
116
|
+
def error(klass = nil)
|
117
|
+
@config.for(:error, klass) { self::Error if const_defined?(:Error) } ||
|
118
|
+
raise(MissingConfigError, "Missing error config for #{self}")
|
119
|
+
end
|
120
|
+
|
121
|
+
# @overload error_constructor()
|
122
|
+
# The callable constructor to build an instance of the +error+ class.
|
123
|
+
# Defaults to {Teckel::DEFAULT_CONSTRUCTOR}
|
124
|
+
# @return [Proc] A callable that will return an instance of +error+ class.
|
125
|
+
#
|
126
|
+
# @overload error_constructor(sym_or_proc)
|
127
|
+
# Define how to build the +error+.
|
128
|
+
# @param sym_or_proc [Symbol, #call]
|
129
|
+
# - Either a +Symbol+ representing the _public_ method to call on the +error+ class.
|
130
|
+
# - Or anything that response to +#call+ (like a +Proc+).
|
131
|
+
# @return [#call] The callable constructor
|
132
|
+
#
|
133
|
+
# @example
|
134
|
+
# class MyOperation
|
135
|
+
# include Teckel::Operation
|
136
|
+
#
|
137
|
+
# class Error
|
138
|
+
# def initialize(*args, **opts); end
|
139
|
+
# end
|
140
|
+
#
|
141
|
+
# # MyOperation.call("foo", "bar") # -> Error.new("foo", "bar")
|
142
|
+
# error_constructor :new
|
143
|
+
#
|
144
|
+
# # If you need more control over how to build a new +Error+ instance
|
145
|
+
# # MyOperation.call("foo", opt: "bar") # -> Error.new(name: "foo", opt: "bar")
|
146
|
+
# error_constructor ->(name, options) { Error.new(name: name, **options) }
|
147
|
+
# end
|
148
|
+
def error_constructor(sym_or_proc = nil)
|
149
|
+
get_set_constructor(:error_constructor, error, sym_or_proc)
|
150
|
+
end
|
151
|
+
|
152
|
+
# @!endgroup
|
153
|
+
|
154
|
+
# @overload settings()
|
155
|
+
# Get the configured class wrapping the settings data structure.
|
156
|
+
# @return [Class] The +settings+ class, or {Teckel::Contracts::None} as default
|
157
|
+
#
|
158
|
+
# @overload settings(klass)
|
159
|
+
# Set the class wrapping the settings data structure.
|
160
|
+
# @param klass [Class] The +settings+ class
|
161
|
+
# @return [Class] The +settings+ class configured
|
162
|
+
def settings(klass = nil)
|
163
|
+
@config.for(:settings, klass) { const_defined?(:Settings) ? self::Settings : none }
|
164
|
+
end
|
165
|
+
|
166
|
+
# @overload settings_constructor()
|
167
|
+
# The callable constructor to build an instance of the +settings+ class.
|
168
|
+
# Defaults to {Teckel::DEFAULT_CONSTRUCTOR}
|
169
|
+
# @return [Proc] A callable that will return an instance of +settings+ class.
|
170
|
+
#
|
171
|
+
# @overload settings_constructor(sym_or_proc)
|
172
|
+
# Define how to build the +settings+.
|
173
|
+
# @param sym_or_proc [Symbol, #call]
|
174
|
+
# - Either a +Symbol+ representing the _public_ method to call on the +settings+ class.
|
175
|
+
# - Or anything that response to +#call+ (like a +Proc+).
|
176
|
+
# @return [#call] The callable constructor
|
177
|
+
#
|
178
|
+
# @example
|
179
|
+
# class MyOperation
|
180
|
+
# include Teckel::Operation
|
181
|
+
#
|
182
|
+
# class Settings
|
183
|
+
# def initialize(*args); end
|
184
|
+
# end
|
185
|
+
#
|
186
|
+
# # MyOperation.with("foo", "bar") # -> Settings.new("foo", "bar")
|
187
|
+
# settings_constructor :new
|
188
|
+
# end
|
189
|
+
def settings_constructor(sym_or_proc = nil)
|
190
|
+
get_set_constructor(:settings_constructor, settings, sym_or_proc) ||
|
191
|
+
raise(MissingConfigError, "Missing settings_constructor config for #{self}")
|
192
|
+
end
|
193
|
+
|
194
|
+
# Declare default settings this operation should use when called without
|
195
|
+
# {Teckel::Operation::ClassMethods#with #with}.
|
196
|
+
# When executing a Operation, +settings+ will no longer be +nil+, but
|
197
|
+
# whatever you define here.
|
198
|
+
#
|
199
|
+
# Explicit call-time settings will *not* get merged with declared default setting.
|
200
|
+
#
|
201
|
+
# @overload default_settings!()
|
202
|
+
# When this operation is called without {Teckel::Operation::ClassMethods#with #with},
|
203
|
+
# +settings+ will be an instance of the +settings+ class, initialized with no arguments.
|
204
|
+
#
|
205
|
+
# @overload default_settings!(sym_or_proc)
|
206
|
+
# When this operation is called without {Teckel::Operation::ClassMethods#with #with},
|
207
|
+
# +settings+ will be an instance of this callable constructor.
|
208
|
+
#
|
209
|
+
# @param sym_or_proc [Symbol, #call]
|
210
|
+
# - Either a +Symbol+ representing the _public_ method to call on the +settings+ class.
|
211
|
+
# - Or anything that responds to +#call+ (like a +Proc+).
|
212
|
+
#
|
213
|
+
# @overload default_settings!(arg1, arg2, ...)
|
214
|
+
# When this operation is called without {Teckel::Operation::ClassMethods#with #with},
|
215
|
+
# +settings+ will be an instance of the +settings+ class, initialized with those arguments.
|
216
|
+
#
|
217
|
+
# (Like calling +MyOperation.with(arg1, arg2, ...)+)
|
218
|
+
def default_settings!(*args)
|
219
|
+
callable = if args.size.equal?(1)
|
220
|
+
build_constructor(settings, args.first)
|
221
|
+
end
|
222
|
+
|
223
|
+
callable ||= -> { settings_constructor.call(*args) }
|
224
|
+
|
225
|
+
@config.for(:default_settings, callable)
|
226
|
+
end
|
227
|
+
|
228
|
+
# Getter for configured default settings
|
229
|
+
# @return [nil|#call] The callable constructor
|
230
|
+
def default_settings
|
231
|
+
@config.for(:default_settings)
|
232
|
+
end
|
233
|
+
|
234
|
+
# @overload runner()
|
235
|
+
# @return [Class] The Runner class
|
236
|
+
# @!visibility protected
|
237
|
+
#
|
238
|
+
# @overload runner(klass)
|
239
|
+
# Overwrite the default runner
|
240
|
+
# @param klass [Class] A class like the {Runner}
|
241
|
+
# @!visibility protected
|
242
|
+
def runner(klass = nil)
|
243
|
+
@config.for(:runner, klass) { Runner }
|
244
|
+
end
|
245
|
+
|
246
|
+
# @overload result()
|
247
|
+
# Get the configured result object class wrapping {error} or {output}.
|
248
|
+
# The {ValueResult} default will act as a pass-through and does. Any error
|
249
|
+
# or output will just returned as-is.
|
250
|
+
# @return [Class] The +result+ class, or {ValueResult} as default
|
251
|
+
#
|
252
|
+
# @overload result(klass)
|
253
|
+
# Set the result object class wrapping {error} or {output}.
|
254
|
+
# @param klass [Class] The +result+ class
|
255
|
+
# @return [Class] The +result+ class configured
|
256
|
+
def result(klass = nil)
|
257
|
+
@config.for(:result, klass) { const_defined?(:Result, false) ? self::Result : ValueResult }
|
258
|
+
end
|
259
|
+
|
260
|
+
# @overload result_constructor()
|
261
|
+
# The callable constructor to build an instance of the +result+ class.
|
262
|
+
# Defaults to {Teckel::DEFAULT_CONSTRUCTOR}
|
263
|
+
# @return [Proc] A callable that will return an instance of +result+ class.
|
264
|
+
#
|
265
|
+
# @overload result_constructor(sym_or_proc)
|
266
|
+
# Define how to build the +result+.
|
267
|
+
# @param sym_or_proc [Symbol, #call]
|
268
|
+
# - Either a +Symbol+ representing the _public_ method to call on the +result+ class.
|
269
|
+
# - Or anything that response to +#call+ (like a +Proc+).
|
270
|
+
# @return [#call] The callable constructor
|
271
|
+
#
|
272
|
+
# @example
|
273
|
+
# class MyOperation
|
274
|
+
# include Teckel::Operation
|
275
|
+
#
|
276
|
+
# class Result
|
277
|
+
# include Teckel::Result
|
278
|
+
# def initialize(value, success, opts = {}); end
|
279
|
+
# end
|
280
|
+
#
|
281
|
+
# # If you need more control over how to build a new +Result+ instance
|
282
|
+
# result_constructor ->(value, success) { result.new(value, success, {foo: :bar}) }
|
283
|
+
# end
|
284
|
+
def result_constructor(sym_or_proc = nil)
|
285
|
+
get_set_constructor(:result_constructor, result, sym_or_proc) ||
|
286
|
+
raise(MissingConfigError, "Missing result_constructor config for #{self}")
|
287
|
+
end
|
288
|
+
|
289
|
+
# @!group Shortcuts
|
290
|
+
|
291
|
+
# Shortcut to use {Teckel::Operation::Result} as a result object,
|
292
|
+
# wrapping any {error} or {output}.
|
293
|
+
#
|
294
|
+
# @!visibility protected
|
295
|
+
# @note Don't use in conjunction with {result} or {result_constructor}
|
296
|
+
# @return [nil]
|
297
|
+
def result!
|
298
|
+
@config.for(:result, Result)
|
299
|
+
@config.for(:result_constructor, Result.method(:new))
|
300
|
+
nil
|
301
|
+
end
|
302
|
+
|
303
|
+
# @!visibility private
|
304
|
+
REQUIRED_CONFIGS = %i[
|
305
|
+
input input_constructor
|
306
|
+
output output_constructor
|
307
|
+
error error_constructor
|
308
|
+
settings settings_constructor
|
309
|
+
result result_constructor
|
310
|
+
runner
|
311
|
+
].freeze
|
312
|
+
|
313
|
+
# @!visibility private
|
314
|
+
# @return [void]
|
315
|
+
def define!
|
316
|
+
REQUIRED_CONFIGS.each { |e| public_send(e) }
|
317
|
+
nil
|
318
|
+
end
|
319
|
+
|
320
|
+
# Disallow any further changes to this Operation.
|
321
|
+
# Make sure all configurations are set.
|
322
|
+
#
|
323
|
+
# @raise [MissingConfigError]
|
324
|
+
# @return [self] Frozen self
|
325
|
+
# @!visibility public
|
326
|
+
def finalize!
|
327
|
+
define!
|
328
|
+
@config.freeze
|
329
|
+
self
|
330
|
+
end
|
331
|
+
|
332
|
+
# Produces a shallow copy of this operation and all it's configuration.
|
333
|
+
#
|
334
|
+
# @return [self]
|
335
|
+
# @!visibility public
|
336
|
+
def dup
|
337
|
+
dup_config(super())
|
338
|
+
end
|
339
|
+
|
340
|
+
# Produces a clone of this operation and all it's configuration
|
341
|
+
#
|
342
|
+
# @return [self]
|
343
|
+
# @!visibility public
|
344
|
+
def clone
|
345
|
+
if frozen?
|
346
|
+
super()
|
347
|
+
else
|
348
|
+
dup_config(super())
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
# Prevents further modifications to this operation and its config
|
353
|
+
#
|
354
|
+
# @return [self]
|
355
|
+
# @!visibility public
|
356
|
+
def freeze
|
357
|
+
@config.freeze
|
358
|
+
super()
|
359
|
+
end
|
360
|
+
|
361
|
+
# @!visibility private
|
362
|
+
def inherited(subclass)
|
363
|
+
super(dup_config(subclass))
|
364
|
+
end
|
365
|
+
|
366
|
+
# @!visibility private
|
367
|
+
def self.extended(base)
|
368
|
+
base.instance_exec do
|
369
|
+
@config = Teckel::Config.new
|
370
|
+
attr_accessor :runner
|
371
|
+
attr_accessor :settings
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
private
|
376
|
+
|
377
|
+
def dup_config(other_class)
|
378
|
+
other_class.instance_variable_set(:@config, @config.dup)
|
379
|
+
other_class
|
380
|
+
end
|
381
|
+
|
382
|
+
def get_set_constructor(name, on, sym_or_proc)
|
383
|
+
constructor = build_constructor(on, sym_or_proc)
|
384
|
+
|
385
|
+
@config.for(name, constructor) {
|
386
|
+
build_constructor(on, DEFAULT_CONSTRUCTOR)
|
387
|
+
}
|
388
|
+
end
|
389
|
+
|
390
|
+
def build_constructor(on, sym_or_proc)
|
391
|
+
case sym_or_proc
|
392
|
+
when Proc
|
393
|
+
sym_or_proc
|
394
|
+
when Symbol
|
395
|
+
on.public_method(sym_or_proc) if on.respond_to?(sym_or_proc)
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|