teckel 0.4.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 18179b7fd315a11bc51f0ac4cfbdba4c0e74ad82a6a7dc186289433906ec2783
4
- data.tar.gz: 02eb5f5c59ac9983a92b457f9253a38f9188e3f2b17d4f30029aa545b3151181
3
+ metadata.gz: bf2c470202a0d90fbc79ed7d6c7e065634f34f6237d01d8d9772489f7ba43e94
4
+ data.tar.gz: 0b2937e820b2f61bf37d835521c6b981e95e50c3f1ef4d5e968ee38dfea7c5fa
5
5
  SHA512:
6
- metadata.gz: cbbbebfecccf6806da642dbc1dce843946c7dcaa27abb7c38467b0512ff5cb32d13b848fae0eaa0f8c4641f26e5b1f03f58f82fb70b3aa8aa1a891356698ff16
7
- data.tar.gz: b312534680f31d7cff2a13326f3dc3cc01566110d3001b285d233c97548edbfb2fe008d59821ef0723b63687f12e95cca3f06f2f7c1a3f8d0e15d90caca4733d
6
+ metadata.gz: 3cf2096a7827c2547e6c4cc135657d68dcc597d057e202d0c8cda91753eee09648133e3c6728a3ae1ec14f140fe562b2648ae77439cccfa015a5130326557fba
7
+ data.tar.gz: 5f13605ac5dc726ce964a85225d1e860a22d00b8eabae78616fa998e77c94ec7251f1367f07175868e8724477bc95c27945d93e23bee0da68303a452a1180e09
@@ -1,5 +1,41 @@
1
1
  # Changes
2
2
 
3
+ ## 0.5.0
4
+
5
+ - Fix: calling chain with settings and no input [GH-14]
6
+ - Add: Default settings for Operation and Chains [GH-17], [GH-18]
7
+ ```ruby
8
+ class MyOperation
9
+ include Teckel::Operation
10
+
11
+ settings Struct.new(:logger)
12
+
13
+ # If your settings class can cope with no input and you want to make sure
14
+ # `settings` gets initialized and set.
15
+ # settings will be #<struct logger=nil>
16
+ default_settings!
17
+
18
+ # settings will be #<struct logger=MyGlobalLogger>
19
+ default_settings!(MyGlobalLogger)
20
+
21
+ # settings will be #<struct logger=#<Logger:<...>>
22
+ default_settings! -> { settings.new(Logger.new("/tmp/my.log")) }
23
+ end
24
+
25
+ class Chain
26
+ include Teckel::Chain
27
+
28
+ # set or overwrite operation settings
29
+ default_settings!(a: MyOtherLogger)
30
+
31
+ step :a, MyOperation
32
+ end
33
+ ```
34
+
35
+ Internal:
36
+ - Move operation and chain config dsl methods into own module [GH-15]
37
+ - Code simplifications [GH-16]
38
+
3
39
  ## 0.4.0
4
40
 
5
41
  - Moving verbose examples from API docs into github pages
@@ -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
- runner = self.runner.new(self)
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,81 +71,10 @@ 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)
77
+ receiver.extend Config
256
78
  receiver.extend ClassMethods
257
79
  end
258
80
  end
@@ -0,0 +1,246 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Teckel
4
+ module Chain
5
+ module Config
6
+ # Declare a {Operation} as a named step
7
+ #
8
+ # @param name [String,Symbol] The name of the operation.
9
+ # This name is used in an error case to let you know which step failed.
10
+ # @param operation [Operation] The operation to call, which
11
+ # must return a {Teckel::Result} object.
12
+ def step(name, operation)
13
+ steps << Step.new(name, operation)
14
+ end
15
+
16
+ # Get the list of defined steps
17
+ #
18
+ # @return [<Step>]
19
+ def steps
20
+ @config.for(:steps) { [] }
21
+ end
22
+
23
+ # Set or get the optional around hook.
24
+ # A Hook might be given as a block or anything callable. The execution of
25
+ # the chain is yielded to this hook. The first argument being the callable
26
+ # chain ({Runner}) and the second argument the +input+ data. The hook also
27
+ # needs to return the result.
28
+ #
29
+ # @param callable [Proc,{#call}] The hook to pass chain execution control to. (nil)
30
+ #
31
+ # @return [Proc,{#call}] The configured hook
32
+ #
33
+ # @example Around hook with block
34
+ # OUTPUTS = []
35
+ #
36
+ # class Echo
37
+ # include ::Teckel::Operation
38
+ # result!
39
+ #
40
+ # input Hash
41
+ # output input
42
+ #
43
+ # def call(hsh)
44
+ # hsh
45
+ # end
46
+ # end
47
+ #
48
+ # class MyChain
49
+ # include Teckel::Chain
50
+ #
51
+ # around do |chain, input|
52
+ # OUTPUTS << "before start"
53
+ # result = chain.call(input)
54
+ # OUTPUTS << "after start"
55
+ # result
56
+ # end
57
+ #
58
+ # step :noop, Echo
59
+ # end
60
+ #
61
+ # result = MyChain.call(some: 'test')
62
+ # OUTPUTS #=> ["before start", "after start"]
63
+ # result.success #=> { some: "test" }
64
+ def around(callable = nil, &block)
65
+ @config.for(:around, callable || block)
66
+ end
67
+
68
+ # @!attribute [r] runner()
69
+ # @return [Class] The Runner class
70
+ # @!visibility protected
71
+
72
+ # Overwrite the default runner
73
+ # @param klass [Class] A class like the {Runner}
74
+ # @!visibility protected
75
+ def runner(klass = nil)
76
+ @config.for(:runner, klass) { Runner }
77
+ end
78
+
79
+ # @overload result()
80
+ # Get the configured result object class wrapping {.error} or {.output}.
81
+ # @return [Class] The +result+ class, or {Teckel::Chain::Result} as default
82
+ #
83
+ # @overload result(klass)
84
+ # Set the result object class wrapping {.error} or {.output}.
85
+ # @param klass [Class] The +result+ class
86
+ # @return [Class] The +result+ class configured
87
+ def result(klass = nil)
88
+ @config.for(:result, klass) { const_defined?(:Result, false) ? self::Result : Teckel::Chain::Result }
89
+ end
90
+
91
+ # @overload result_constructor()
92
+ # The callable constructor to build an instance of the +result+ class.
93
+ # Defaults to {Teckel::DEFAULT_CONSTRUCTOR}
94
+ # @return [Proc] A callable that will return an instance of +result+ class.
95
+ #
96
+ # @overload result_constructor(sym_or_proc)
97
+ # Define how to build the +result+.
98
+ # @param sym_or_proc [Symbol, #call]
99
+ # - Either a +Symbol+ representing the _public_ method to call on the +result+ class.
100
+ # - Or anything that response to +#call+ (like a +Proc+).
101
+ # @return [#call] The callable constructor
102
+ #
103
+ # @example
104
+ # class MyOperation
105
+ # include Teckel::Operation
106
+ #
107
+ # class Result < Teckel::Operation::Result
108
+ # def initialize(value, success, step, options = {}); end
109
+ # end
110
+ #
111
+ # # If you need more control over how to build a new +Settings+ instance
112
+ # result_constructor ->(value, success, step) { result.new(value, success, step, {foo: :bar}) }
113
+ # end
114
+ def result_constructor(sym_or_proc = nil)
115
+ constructor = build_constructor(result, sym_or_proc) unless sym_or_proc.nil?
116
+
117
+ @config.for(:result_constructor, constructor) {
118
+ build_constructor(result, Teckel::DEFAULT_CONSTRUCTOR)
119
+ } || raise(MissingConfigError, "Missing result_constructor config for #{self}")
120
+ end
121
+
122
+ # Declare default settings operation iin this chain should use when called without
123
+ # {Teckel::Chain::ClassMethods#with #with}.
124
+ #
125
+ # Explicit call-time settings will *not* get merged with declared default setting.
126
+ #
127
+ # @param settings [Hash{String,Symbol => Object}] Set settings for a step by it's name
128
+ #
129
+ # @example
130
+ # class MyOperation
131
+ # include Teckel::Operation
132
+ # result!
133
+ #
134
+ # settings Struct.new(:say, :other)
135
+ # settings_constructor ->(data) { settings.new(*data.values_at(*settings.members)) }
136
+ #
137
+ # input none
138
+ # output Hash
139
+ # error none
140
+ #
141
+ # def call(_)
142
+ # settings.to_h
143
+ # end
144
+ # end
145
+ #
146
+ # class Chain
147
+ # include Teckel::Chain
148
+ #
149
+ # default_settings!(a: { say: "Chain Default" })
150
+ #
151
+ # step :a, MyOperation
152
+ # end
153
+ #
154
+ # # Using the chains default settings
155
+ # result = Chain.call
156
+ # result.success #=> {say: "Chain Default", other: nil}
157
+ #
158
+ # # explicit settings passed via `with` will overwrite all defaults
159
+ # result = Chain.with(a: { other: "What" }).call
160
+ # result.success #=> {say: nil, other: "What"}
161
+ def default_settings!(settings) # :nodoc: The bang is for consistency with the Operation class
162
+ @config.for(:default_settings, settings)
163
+ end
164
+
165
+ # Getter for configured default settings
166
+ # @return [nil|#call] The callable constructor
167
+ def default_settings
168
+ @config.for(:default_settings)
169
+ end
170
+
171
+ REQUIRED_CONFIGS = %i[around runner result result_constructor].freeze
172
+
173
+ # @!visibility private
174
+ # @return [void]
175
+ def define!
176
+ raise MissingConfigError, "Cannot define Chain with no steps" if steps.empty?
177
+
178
+ REQUIRED_CONFIGS.each { |e| public_send(e) }
179
+ steps.each(&:finalize!)
180
+ nil
181
+ end
182
+
183
+ # Disallow any further changes to this Chain.
184
+ # @note This also calls +finalize!+ on all Operations defined as steps.
185
+ #
186
+ # @return [self] Frozen self
187
+ # @!visibility public
188
+ def finalize!
189
+ define!
190
+ steps.freeze
191
+ @config.freeze
192
+ self
193
+ end
194
+
195
+ # Produces a shallow copy of this chain.
196
+ # It's {around}, {runner} and {steps} will get +dup+'ed
197
+ #
198
+ # @return [self]
199
+ # @!visibility public
200
+ def dup
201
+ dup_config(super)
202
+ end
203
+
204
+ # Produces a clone of this chain.
205
+ # It's {around}, {runner} and {steps} will get +dup+'ed
206
+ #
207
+ # @return [self]
208
+ # @!visibility public
209
+ def clone
210
+ if frozen?
211
+ super
212
+ else
213
+ dup_config(super)
214
+ end
215
+ end
216
+
217
+ # @!visibility private
218
+ def inherited(subclass)
219
+ dup_config(subclass)
220
+ end
221
+
222
+ # @!visibility private
223
+ def self.extended(base)
224
+ base.instance_variable_set(:@config, Teckel::Config.new)
225
+ end
226
+
227
+ private
228
+
229
+ def dup_config(other_class)
230
+ new_config = @config.dup
231
+ new_config.replace(:steps) { steps.dup }
232
+
233
+ other_class.instance_variable_set(:@config, new_config)
234
+ other_class
235
+ end
236
+
237
+ def build_constructor(on, sym_or_proc)
238
+ if sym_or_proc.is_a?(Symbol) && on.respond_to?(sym_or_proc)
239
+ on.public_method(sym_or_proc)
240
+ elsif sym_or_proc.respond_to?(:call)
241
+ sym_or_proc
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end