reduxco 1.0.0 → 1.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 66dee8a1deb9af6c20d475fb99e8e7548c5d2188
4
+ data.tar.gz: c0ddc9771da228833b264c798a3d532491e306fa
5
+ SHA512:
6
+ metadata.gz: 6804b82d9cefbe4d5a6439e1dbaf225230a89732544dbfc68ffcb45501f616c13ad2195fd66d57fd6e23ab2e76f3db08670b13624ef835120bea781578a2875e
7
+ data.tar.gz: 4298d0af571f7c98ab7018664791792b8da6f778b82169759d1d423361a35f683d71b7a25cd9d5561a1f0897a1bc44e9154aaea7d69b93f82c1f3f7cceb2e9e2
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012, WhitePages, Inc.
1
+ Copyright (c) 2013, WhitePages, Inc.
2
2
  All rights reserved.
3
3
 
4
4
  Redistribution and use in source and binary forms, with or without
@@ -1,24 +1,221 @@
1
+ = Introduction to Reduxco
2
+
3
+ == What is Reduxco?
4
+
5
+ In a sentence, Reduxco is a general purpose graph reduction engine that is
6
+ perfect for pipelines and calculation engines.
7
+
8
+ At its core, Reduxco allows you to define interchangeable graph nodes and easily
9
+ evalute them, producing one or more results. Key features include:
10
+ * Interchangeable, modular nodes,
11
+ * Self-organizing graph,
12
+ * Lazy evaluation of nodes,
13
+ * Node value caching,
14
+ * Node Overriding and inheritance,
15
+ * Helpers for nesting, and
16
+ * Graph Introspection and Reflection.
17
+
18
+ == Why would I use Reduxco?
19
+
20
+ The prototypical example of graph reduction is in the reduction of expression
21
+ graphs, whereby each node represents one part of a larger calculation, and the
22
+ final result is the final product of the calculation.
23
+
24
+ But what Reduxco is best at are pipelines.
25
+
26
+ Consider Rack Middleware: it is a list of interchangeable black-box like
27
+ "nodes" that form a response generation pipeline.
28
+ Now consider this: it often becomes necessary to communicate intermediate
29
+ information between various components. This requires a sideband channel
30
+ (e.g. the rack env) to store this data. This has a couple of problems, including
31
+ a lack of uniform access to data (i.e. did it come from the pipeline or the
32
+ side-band data?). Even worse, a mixup in the order of middleware can cause
33
+ errors due to missing dependencies in the sideband channel.
34
+
35
+ Reduxco solves these problems for you, by self organizing the dependency graph
36
+ thanks to a lazy evaluation model that gets the value of each node as it is
37
+ needed.
38
+
39
+ == Where is Reduxco Being Used?
40
+
41
+ Reduxco is used to build the request/response pipelines for the Whitepages APIs;
42
+ it handles everything from taking of the initial request parameters, all the
43
+ way through generating and returning the final formatted output.
44
+
45
+ It's modularity and customizability are used to great effect, allowing us to
46
+ easily refactor or replace nodes to drastically change the result of a an API
47
+ call, without having to worry about its effect on the rest of the work the
48
+ request does.
49
+
50
+ == How do I use Reduxco?
51
+
52
+ At its core, Reduxco allows you to build a named set of "callables" (i.e. any
53
+ object that responds to the <code>call</code> method, such as a Proc), and
54
+ evaluate the values from one or more nodes.
55
+
56
+ To give the rich set of features Reduxco offers, callables must take a single
57
+ argument to their <code>call</code> method which contains a Reduxco::Context. The
58
+ Context is essentially a little runtime, responsible for dynamically routing
59
+ calls to dependencies, and caching the results.
60
+
61
+ Lastly, Reduxco pipelines are created via the Reduxco::Reduxer class.
62
+
63
+ Thus, as a contrived simpleexample, let's do a simple expression graph reduction on
64
+ the equation <code>(x+y) * (x+y)</code>, with the values of x and y generated
65
+ at random:
66
+
67
+ callables = {
68
+ x: ->(c){ rand(10) },
69
+ y: ->(c){ rand(10) },
70
+ sum: ->(c){ c[:x] + c[:y] },
71
+ result: ->(c){ c[:sum] * c[:sum] },
72
+ app: ->(c){ "For x=#{c[:x]} and y=#{c[:y]}, the result is #{c[:result]}." }
73
+ }
74
+
75
+ pipeline = Reduxco::Reduxer.new(callables)
76
+ output = pipeline.reduce
77
+ output.should == "For x=3 and y=6, the result is 81."
78
+
79
+ This example starts by defining named callables as Ruby blocks, and then creating
80
+ a Reduxco pipeline out of it. The call to <code>reduce</code> invokes calculation
81
+ of the <code>:app</code> node, which cascades into evaluation of the required
82
+ portions of the graph. Note that the result is the square of the sum,
83
+ demonstrating that the <code>:x</code> and <code>:y</code> nodes are only
84
+ evaluated once and cached.
85
+
86
+ = Example
87
+
88
+ This example demonstrates some more advanced features of Reduxco, such as
89
+ instrospection, yielding values, flow helpers, and overriding.
90
+
91
+ Consider the basic structure for an application with error
92
+ handling, and that has the following requirements:
93
+ * For the default gut implementation, we generate a random number.
94
+ * If that number is even, then we raise a RuntimeError.
95
+ * If that number is odd, we return the number.
96
+
97
+ Furthermore, the consumer of the pipeline must define its own error handling
98
+ strategy as so:
99
+ * The error handling layer should catch the error and substitute the error
100
+ message string as the result.
101
+
102
+ Lastly, to write tests against this, we nee to override the value to be an even
103
+ for one test, and an odd for the next test.
104
+
105
+ The finished code looks like this
106
+
107
+ # The base callables, probably served up from a factory.
108
+ base_callables = {
109
+ app: ->(c) do
110
+ c.inside(:error_handler) do
111
+ c[:value].even? ? raise(RuntimeError, "Even!") : c[:value]
112
+ end
113
+ end,
114
+
115
+ error_handler: ->(c) do
116
+ begin
117
+ c.yield
118
+ rescue => error
119
+ c.call(:onfailure){error} if c.include?(:onfailure)
120
+ end
121
+ end,
122
+
123
+ value: ->(c){ rand(100) },
124
+ }
125
+
126
+ # The contect specific eror handler implementation.
127
+ handler_callables = {
128
+ onfailure: ->(c){ c.yield.message }
129
+ }
130
+
131
+ # Test callables; overrieds the value to be an even value.
132
+ even_test_callables = {
133
+ value: ->(c){ 8 }
134
+ }
135
+
136
+ # Test callables: overrieds the value to be an odd value.
137
+ odd_test_callables = {
138
+ value: ->(c){ 13 }
139
+ }
140
+
141
+ # Test evens
142
+ pipeline = Reduxco::Reduxer.new(base_callables, handler_callables, even_test_callables)
143
+ pipeline.reduce.should == 'Even!'
144
+
145
+ # Test odds
146
+ pipeline = Reduxco::Reduxer.new(base_callables, handler_callables, odd_test_callables)
147
+ pipeline.reduce.should == 13
148
+
149
+ # Invoke with random result
150
+ pipeline = Reduxco::Reduxer.new(base_callables, handler_callables)
151
+ random_result = pipeline.reduce
152
+
153
+ There are a few features to note about this code, with each explained in more
154
+ detail below:
155
+ * When multiple callable maps are given during pipeline instantiation, the
156
+ Reduxco::Context dispatches the the right-most map with the needed callable. Not shown
157
+ here is the ability to call <code>c.super</code> to get the value for a given
158
+ callable in the next highest map.
159
+ * The error handler demonstrates the use of introspection via the Reduxco::Context#include?
160
+ method, which checks that a given name is available.
161
+ Not shown are several other inspection methods, including introspection
162
+ as to which nodes are evaluated.
163
+ * The error handler utilizes the Reduxco::Context#yield method, which
164
+ yields the value of the block provided on the associated Reduxco::Context#call
165
+ method, in this case the error passed to the
166
+ <code>:onfailure</code> callable when invoked in the error handler.
167
+ * The use of the convenience method Reduxco::Context#inside, which although is the
168
+ same as Reduxco::Context#call, expresses the meaning of the code better.
169
+
1
170
  = Overview
2
171
 
3
- Reduxco is a general purpose graph reduction calculation engine for those
4
- non-linear dependency flows that normal pipelines and Rack Middleware-like
5
- architectures can't do cleanly.
172
+ Reduxco is a graph reduction engine on steroids. It allows the creation of
173
+ maps of callables to create a self-organizing, lazy evaluated pipeline.
174
+
175
+ The main two classes are the Reduxco::Reduxer class, which is used to instantiate
176
+ pipelines, and the Reduxco::Context class, which is coordinates communication
177
+ between the nodes.
6
178
 
7
- Conceptually, it is similar to using Rack Middleware with named keys to store
8
- intermediate calculations that have to be reused later, but unlike Rack Middleware,
9
- Reduxco is self organizing based on the dependencies used by each piece.
179
+ == Callable
10
180
 
11
- It's primary public facing class is Reduxco::Context.
181
+ They key building block of Reduxco pipelines are named "callables", which become
182
+ the implementation logic for each node in the graph.
12
183
 
13
- = Examples
184
+ There is no specific callable class. To be a callable, an object need comply
185
+ with the following two rules:
186
+ 1. The object must respond to the <code>call</code> method.
187
+ 2. The <code>call</code> method must take a single argument, which is a
188
+ Reduxco::Context instance.
14
189
 
15
- == Basic Context Use
190
+ Most of the time, the standard Ruby Proc object is all that is necessary, but
191
+ there are many clever reasons why one may substitute in a specialty object
192
+ in its place.
16
193
 
17
- In practice, one can build one ore more tables of callable objects (e.g. Procs or
18
- custom class instances), and register them with a Reduxco::Context. Callables can then
19
- used their Reduco::Context handle to refer to the callables they can depend on.
194
+ == Reduxco::Reduxer
20
195
 
21
- For example, the addition of two numbers could be done as follows:
196
+ The Reduxco::Reduxer class is used to instantiate new pipelines, and to get
197
+ values from the pipeline.
198
+
199
+ === Pipeline Creation
200
+
201
+ Reduxco::Reduxer instances are created by passing one or more maps of callables
202
+ during instantiation.
203
+
204
+ Callable maps are usually Hash instance, whose keys are Symbol instances, and
205
+ values meet the requirements of callables.
206
+
207
+ If more than one map is provieded to the initializer, callables are resolved
208
+ to the right-most argument that defines it. This provides a mechanism for
209
+ overriding callables created by a factory to customize for your layer. See
210
+ the section on overriding below.
211
+
212
+ === Pipeline Invocation
213
+
214
+ The resulting Pipeline instance is considered an immutable object whose values
215
+ are lazily evaluated as necessary and then cached. These values are extracted
216
+ via the Reduxco::Reduxer#reduce method.
217
+
218
+ For example, a simple pipeline can be instantiated with the following code:
22
219
 
23
220
  map = {
24
221
  sum: ->(c){ c[:x] + c[:y] },
@@ -26,24 +223,84 @@ For example, the addition of two numbers could be done as follows:
26
223
  y: ->(c){ 5 }
27
224
  }
28
225
 
29
- sum = Reduxco::Reduxer.new(map).reduce(:sum)
226
+ pipeline = Reduxco::Reduxer.new(map)
227
+ sum = pipeline.reduce(:sum)
30
228
  sum.should == 8
31
229
 
32
- Note that the symbol <code>:app</code> is the default root node of Reduxco::Context#reduce,
33
- so if <code>:sum</code> were renamed to <code>:app</code> above, the last line could
34
- be slightly simplified as:
230
+ Note that while Reduxco::Reduxer#reduce can take any named callable as an
231
+ argument, it by default attempts to reduce the value of the <code>:app</code>
232
+ callable when called with no argument.
233
+
234
+ Thus, most practical pipelines define an <code>:app</code> callable and simply
235
+ call Reduxco::Reduxer#reduce without an explicit argument:
236
+
237
+ pipeline = Reduxco::Reduxer.new(app: ->(c){ "Hello World" })
238
+ result = pipeline.reduce
239
+ result.should == "Hello World"
240
+
241
+ == Reduxco::Context
242
+
243
+ The Reduxco::Context object is the workhorse of the pipeline. It is responsible
244
+ for communication between the node including invoking the correct callables as
245
+ necessary and caching their results.
35
246
 
36
- sum = Reduxco::Context.new(map).reduce
247
+ The Reduxco::Context instance is passed into each node's callable when it is
248
+ invoked, allowing for a plethora of communication and helper methods to be
249
+ used by the callables. An overview of this functionality is presented below.
250
+
251
+ == Basic Calling
252
+
253
+ The most common use of the Reduxco::Context object is to retrieve values of
254
+ other callables. This is accomplished via one of two methods:
255
+ * Reduxco::Context#[], which is the preferred way to call due to readability.
256
+ * Reduxco::Context#call, which behaves exactly the same, but has the option of
257
+ taking a block that can be evaluated by the called callable (explained below)
258
+
259
+ Don't forget that callables are only evaluated once and then cached, so multiple
260
+ retrievals of complex computations are as efficient as possible.
261
+
262
+ == Yielding Values
263
+
264
+ Sometimes one needs to push values into a callable when it is called. A good
265
+ example of this are error handling hooks, which are invoked when an error is
266
+ caught, and must be passed the error for processing.
267
+
268
+ Reduxco::Context#yield provides functionality to do this, but allowing the
269
+ callable to execute the block passed into the associated Reduxco::Context#call
270
+ method, and retrieve its value.
271
+
272
+ For example, consider the following pipeline:
273
+
274
+ callables = {
275
+ app: ->(c){ c.call(:foo) {3+20} },
276
+ foo: ->(c){ c.yield + 100 }
277
+ }
278
+
279
+ pipeline = Reduxco::Reduxer.new(callables)
280
+ pipeline.reduce.should == 123
37
281
 
38
- Of course, any object responding to <code>call</code> can be used as the values in
39
- the map, so one could just as easily define a class with an instance method of
40
- <code>call</code> on it instead of using Proc objects.
41
282
 
42
283
  == Overriding and Super
43
284
 
44
- If multiple maps of callables are given, and the keys (referred to as names from
45
- here on) are duplicated in the maps, the last map given wins, shadowing the previous map.
46
- For example:
285
+ === Dynamic Dispatch
286
+
287
+ Resolution of callables for a node is done via a dynamic dispatch methodology
288
+ that is not all that different than dynamic method dispatch in object oriented
289
+ dynamic languages like Ruby.
290
+
291
+ The Reduxco::Context looks at its stack of callable maps, and tests each map
292
+ until if finds a matching callable. It then selects that callable, and
293
+ retrieves the associated value from the cache, evaluating the callable itself
294
+ if necessary.
295
+
296
+ === Override
297
+
298
+ This dynamic dispatching can be used to override the callable for a node with
299
+ a new one at instantiation (thus shadowing the previous definition). This is
300
+ especially useful when the primary callables may be generated by a factory, but
301
+ some pipeline customization is needed at the client layer.
302
+
303
+ The following code shows a concise example of overriding:
47
304
 
48
305
  map1 = {
49
306
  message: ->(c){ 'Hello From Map 1' }
@@ -56,8 +313,16 @@ For example:
56
313
  msg = Reduxco::Reduxer.new(map1, map2).reduce(:message)
57
314
  msg.should == 'Hello From Map 2'
58
315
 
59
- If one wishes to refer to previous (shadowed) callables, one can do that using
60
- Context#super. For example:
316
+ === Super
317
+
318
+ As mentioned earlier, the dynamic dispatch model used by Reduxco acts a bit like
319
+ a dynamic object oriented language. The logical extension of this is to allow
320
+ for a shadowing callable to execute the callable it shadows. This is easily
321
+ done via the Reduxco::Context#super method, which tells the dynamic dispatcher
322
+ to call the callable for the same node name, starting with the map "above" you
323
+ in the stack.
324
+
325
+ The following example shows a call to super in the override:
61
326
 
62
327
  map1 = {
63
328
  message: ->(c){ 'Hello From Map 1' }
@@ -67,17 +332,19 @@ Context#super. For example:
67
332
  message: ->(c){ c.super + ' and Hello From Map 2' }
68
333
  }
69
334
 
70
- msg = Reduxco::Context.new(map1, map2).reduce(:message)
335
+ msg = Reduxco::Reduxer.new(map1, map2).reduce(:message)
71
336
  msg.should == 'Hello From Map 1 and Hello From Map 2'
72
337
 
73
- == Introspection
338
+ == Instrospection
74
339
 
75
340
  There are several introspection methods for making assertions about the
76
- Reduxco::Context. These are usually used by callables to inspect their
77
- environment before proceeding.
341
+ Reduxco::Context. These are usually used by callables to inspect their
342
+ environment before proceeding down an execution path.
78
343
 
344
+
345
+ The primary introspection methods are as follows:
79
346
  [Reduxco::Context#include?] Allows you to inspect if the Reduxco::Context
80
- can resolve a given refname if called.
347
+ can resolve a given node name if called.
81
348
  [Reduxco::Context#completed?] Allows you to inspect if the callable associated
82
349
  with a given block name has already been called;
83
350
  useful for assertions about weak dependencies.
@@ -86,21 +353,55 @@ environment before proceeding.
86
353
 
87
354
  == Before, After, and Inside
88
355
 
356
+ While not strictly necessary, it is often useful to control call flow with
357
+ a method call that is more expressive than <code>call</code>. In other words,
358
+ while these methods are trivially implementable with just <code>call</code>,
359
+ it is often more desireable for your code to more directly express your intent
360
+ as an author to help with readability and maintainability of your code.
361
+
362
+ Academically, the key characteristic in common to Reduxco::Context#before,
363
+ Reduxco::Context#after and Reduxco::Context#insid, is that they each allow for
364
+ easy expression of ordered flow control, but with the return value being that
365
+ of the callable initially called.
366
+
367
+ As a practical example, Reduxco::Context#inside is often used to insert a
368
+ genericized error handling node into the app node, as in the following code
369
+ listing:
370
+
371
+ callables = {
372
+ app: ->(c) do
373
+ c.inside(:error_handler) do
374
+ c[:result]
375
+ end
376
+ end,
377
+
378
+ error_handler: ->(c) do
379
+ begin
380
+ c.yield
381
+ rescue => error
382
+ c.call(:onfailure){error} if c.include?(:onfailure)
383
+ raise error
384
+ end
385
+ end
386
+
387
+ result: ->(c) do
388
+ # do something
389
+ end
390
+ }
391
+
89
392
  = Contact
90
393
 
91
394
  Jeff Reinecke <jreinecke@whitepages.com>
92
395
 
93
- = Roadmap
94
-
95
- TBD
96
-
97
396
  = History
98
397
 
99
- [1.0.0 - 2013-Apr-??] Initial Release.
398
+ [1.0.0 - 2013-Apr-18] Initial Release.
399
+ [1.0.1 - 2013-Apr-18] Fixed a bug where calling c.yield in a block given to a
400
+ call would give a stack overflow.
100
401
 
101
402
  = License
102
403
 
103
- Copyright (c) 2012, WhitePages, Inc.
404
+ Copyright (c) 2013, WhitePages, Inc.
104
405
  All rights reserved.
105
406
 
106
407
  Redistribution and use in source and binary forms, with or without
@@ -70,6 +70,7 @@ module Reduxco
70
70
  @cache = {}
71
71
 
72
72
  @block_association_cache = {}
73
+ @yield_frame_depth = 0
73
74
  end
74
75
 
75
76
  # Given a refname, call it for this context and return the result.
@@ -129,11 +130,19 @@ module Reduxco
129
130
 
130
131
  # Yields to the block given to a #Context.call
131
132
  def yield(*args)
132
- block = block_for_frame(current_frame)
133
+ block = block_for_frame(yield_frame)
133
134
  if( block.nil? )
134
135
  raise LocalJumpError, "No block given to yield to.", caller
135
136
  else
136
- block.yield(*args)
137
+ begin
138
+ # If the block call has a context yield inside, resolve that one frame up.
139
+ # This turns out to be what we usually want as we are forwarding a
140
+ # yielded value in the call.
141
+ @yield_frame_depth += 1
142
+ block.call(*args)
143
+ ensure
144
+ @yield_frame_depth -= 1
145
+ end
137
146
  end
138
147
  end
139
148
 
@@ -147,6 +156,10 @@ module Reduxco
147
156
  @callstack.top
148
157
  end
149
158
 
159
+ def yield_frame
160
+ @callstack.peek(@yield_frame_depth)
161
+ end
162
+
150
163
  # Returns a true value if the given refname is defined in this context.
151
164
  #
152
165
  # If given a CallableRef, it returns a true value if the reference is
@@ -221,6 +234,9 @@ module Reduxco
221
234
  raise CyclicalError, "Cyclical dependency on #{frame.inspect} in #{@callstack.rest.top.inspect}", callstack.to_caller(caller[1])
222
235
  end
223
236
 
237
+ # On a new call we start resolving yield blocks at the current frame, so reset to zero.
238
+ @yield_frame_depth = 0
239
+
224
240
  # Recall from cache, or build if necessary.
225
241
  unless( @cache.include?(frame) )
226
242
  @block_association_cache[frame] = block
@@ -28,6 +28,13 @@ module Reduxco
28
28
  @stack.last
29
29
  end
30
30
 
31
+ # Returns the element at a given depth from the top of the stack.
32
+ #
33
+ # A depth of zero corresponds to the top of the stack.
34
+ def peek(depth)
35
+ @stack[-depth - 1]
36
+ end
37
+
31
38
  # Returns true if the callstack contains the given frame
32
39
  def include?(frame)
33
40
  @stack.include?(frame)
@@ -1,4 +1,4 @@
1
1
  module Reduxco
2
2
  # The current version of the Reduxco gem.
3
- VERSION = '1.0.0'
3
+ VERSION = '1.0.1'
4
4
  end
@@ -24,6 +24,13 @@ describe Reduxco::Context::Callstack do
24
24
  @stack.top.should == :top
25
25
  end
26
26
 
27
+ it 'should allow peeking into the stack' do
28
+ @stack.peek(0).should == @stack.top
29
+ @stack.peek(@stack.depth).should be_nil
30
+ @stack.peek(1).should == :middle
31
+ @stack.peek(2).should == :bottom
32
+ end
33
+
27
34
  it 'should pop' do
28
35
  frame = @stack.pop
29
36
 
@@ -610,6 +610,20 @@ describe Reduxco::Context do
610
610
  context.call(:app).should == 25
611
611
  end
612
612
 
613
+ it 'should allow forwarded yielded values to be handed down' do
614
+ $debug = true
615
+ value = []
616
+
617
+ context = Reduxco::Context.new(
618
+ app: ->(c){ c.call(:outter){ value } },
619
+ outter: ->(c){ c.call(:middle){ c.yield } },
620
+ middle: ->(c){ c.call(:inner){ c.yield } },
621
+ inner: ->(c){ c.yield + [:inner] }
622
+ )
623
+
624
+ context.call(:app).should == [:inner]
625
+ end
626
+
613
627
  end
614
628
 
615
629
  end
@@ -4,6 +4,80 @@ describe 'RDoc Examples' do
4
4
 
5
5
  describe 'README.rdoc' do
6
6
 
7
+ it 'should meet the math example' do
8
+ srand(5) # For test predictability
9
+
10
+ ###
11
+
12
+ callables = {
13
+ x: ->(c){ rand(10) },
14
+ y: ->(c){ rand(10) },
15
+ sum: ->(c){ c[:x] + c[:y] },
16
+ result: ->(c){ c[:sum] * c[:sum] },
17
+ app: ->(c){ "For x=#{c[:x]} and y=#{c[:y]}, the result is #{c[:result]}." }
18
+ }
19
+
20
+ pipeline = Reduxco::Reduxer.new(callables)
21
+ output = pipeline.reduce
22
+ output.should == "For x=3 and y=6, the result is 81."
23
+
24
+ ###
25
+
26
+ pipeline.reduce(:sum).should == 9
27
+
28
+ ###
29
+
30
+ srand(Time.now.to_i) # And now randomize better again.
31
+ end
32
+
33
+ it 'should meet the error handling exampmle' do
34
+ # The base callables, probably served up from a factory.
35
+ base_callables = {
36
+ app: ->(c) do
37
+ c.inside(:error_handler) do
38
+ c[:value].even? ? raise(RuntimeError, "Even!") : c[:value]
39
+ end
40
+ end,
41
+
42
+ error_handler: ->(c) do
43
+ begin
44
+ c.yield
45
+ rescue => error
46
+ c.call(:onfailure){error} if c.include?(:onfailure)
47
+ end
48
+ end,
49
+
50
+ value: ->(c){ rand(100) },
51
+ }
52
+
53
+ # The contect specific eror handler implementation.
54
+ handler_callables = {
55
+ onfailure: ->(c){ c.yield.message }
56
+ }
57
+
58
+ # Test callables; overrieds the value to be an even value.
59
+ even_test_callables = {
60
+ value: ->(c){ 8 }
61
+ }
62
+
63
+ # Test callables: overrieds the value to be an odd value.
64
+ odd_test_callables = {
65
+ value: ->(c){ 13 }
66
+ }
67
+
68
+ # Test evens
69
+ pipeline = Reduxco::Reduxer.new(base_callables, handler_callables, even_test_callables)
70
+ pipeline.reduce.should == 'Even!'
71
+
72
+ # Test odds
73
+ pipeline = Reduxco::Reduxer.new(base_callables, handler_callables, odd_test_callables)
74
+ pipeline.reduce.should == 13
75
+
76
+ # Invoke with random result
77
+ pipeline = Reduxco::Reduxer.new(base_callables, handler_callables)
78
+ random_result = pipeline.reduce
79
+ end
80
+
7
81
  it 'should obey basic context use' do
8
82
  map = {
9
83
  sum: ->(c){ c[:x] + c[:y] },
@@ -11,10 +85,27 @@ describe 'RDoc Examples' do
11
85
  y: ->(c){ 5 }
12
86
  }
13
87
 
14
- sum = Reduxco::Reduxer.new(map).reduce(:sum)
88
+ pipeline = Reduxco::Reduxer.new(map)
89
+ sum = pipeline.reduce(:sum)
15
90
  sum.should == 8
16
91
  end
17
92
 
93
+ it 'should have a basic app pipeline example' do
94
+ pipeline = Reduxco::Reduxer.new(app: ->(c){ "Hello World" })
95
+ result = pipeline.reduce
96
+ result.should == "Hello World"
97
+ end
98
+
99
+ it 'should have a simple yield example' do
100
+ callables = {
101
+ app: ->(c){ c.call(:foo) {3+20} },
102
+ foo: ->(c){ c.yield + 100 }
103
+ }
104
+
105
+ pipeline = Reduxco::Reduxer.new(callables)
106
+ pipeline.reduce.should == 123
107
+ end
108
+
18
109
  it 'should override/shadow' do
19
110
  map1 = {
20
111
  message: ->(c){ 'Hello From Map 1' }
@@ -7,7 +7,11 @@ require 'reduxco'
7
7
 
8
8
  # Require the debugger, if present.
9
9
  begin
10
- require 'debugger'
10
+ if( RUBY_VERSION < '2.0.0' )
11
+ require 'debugger'
12
+ else
13
+ require 'byebug'
14
+ end
11
15
  rescue LoadError
12
16
  module Kernel
13
17
  def debugger(*args, &block)
@@ -15,3 +19,4 @@ rescue LoadError
15
19
  end
16
20
  end
17
21
  end
22
+
metadata CHANGED
@@ -1,22 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reduxco
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
5
- prerelease:
4
+ version: 1.0.1
6
5
  platform: ruby
7
6
  authors:
8
7
  - Jeff Reinecke
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-04-18 00:00:00.000000000 Z
11
+ date: 2013-12-04 00:00:00.000000000 Z
13
12
  dependencies: []
14
- description: ! 'Reduxco is a general purpose graph reduction calculation engine for
15
- those
16
-
13
+ description: |-
14
+ Reduxco is a general purpose graph reduction calculation engine for those
17
15
  non-linear dependency flows that normal pipelines and Rack Middleware-like
18
-
19
- architectures can''t do cleanly.'
16
+ architectures can't do cleanly.
20
17
  email:
21
18
  - jreinecke@whitepages.com
22
19
  executables: []
@@ -44,26 +41,25 @@ files:
44
41
  homepage: https://github.com/whitepages/reduxco
45
42
  licenses:
46
43
  - BSD
44
+ metadata: {}
47
45
  post_install_message:
48
46
  rdoc_options: []
49
47
  require_paths:
50
48
  - lib
51
49
  required_ruby_version: !ruby/object:Gem::Requirement
52
- none: false
53
50
  requirements:
54
- - - ! '>='
51
+ - - '>='
55
52
  - !ruby/object:Gem::Version
56
53
  version: '0'
57
54
  required_rubygems_version: !ruby/object:Gem::Requirement
58
- none: false
59
55
  requirements:
60
- - - ! '>='
56
+ - - '>='
61
57
  - !ruby/object:Gem::Version
62
58
  version: '0'
63
59
  requirements: []
64
60
  rubyforge_project:
65
- rubygems_version: 1.8.24
61
+ rubygems_version: 2.1.11
66
62
  signing_key:
67
- specification_version: 3
63
+ specification_version: 4
68
64
  summary: A graph reduction calculation engine.
69
65
  test_files: []