reduxco 1.0.0 → 1.0.1

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