teckel 0.4.0 → 0.5.0

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