deferrable_gratification 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.MIT +21 -0
- data/README.markdown +64 -0
- data/lib/deferrable_gratification.rb +59 -0
- data/lib/deferrable_gratification/bothback.rb +18 -0
- data/lib/deferrable_gratification/combinators.rb +172 -0
- data/lib/deferrable_gratification/combinators/bind.rb +94 -0
- data/lib/deferrable_gratification/default_deferrable.rb +17 -0
- data/lib/deferrable_gratification/fluent.rb +34 -0
- data/lib/deferrable_gratification/primitives.rb +38 -0
- metadata +159 -0
data/LICENSE.MIT
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2011 Rapportive Inc.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# Deferrable Gratification #
|
2
|
+
|
3
|
+
## Purpose ##
|
4
|
+
|
5
|
+
Deferrable Gratification (DG) makes evented code less error-prone and easier to
|
6
|
+
compose, and thus easier to create higher-level abstractions around. It also
|
7
|
+
enhances the API offered by Ruby Deferrables to make them more pleasant to work
|
8
|
+
with.
|
9
|
+
|
10
|
+
## Documentation ##
|
11
|
+
|
12
|
+
* [API](http://samstokes.github.com/deferrable_gratification/doc/frames.html)
|
13
|
+
* [Behaviour specs](http://samstokes.github.com/deferrable_gratification/doc/spec/index.html)
|
14
|
+
(generated from RSpec code examples)
|
15
|
+
|
16
|
+
## Components ##
|
17
|
+
|
18
|
+
It currently consists of the following components:
|
19
|
+
|
20
|
+
* [`DG::Fluent`](#fluent): fluent (aka chainable) syntax for registering
|
21
|
+
multiple callbacks and errbacks to the same Deferrable.
|
22
|
+
|
23
|
+
* [`DG::Bothback`](#bothback): a `#bothback` method for registering code to
|
24
|
+
run on either success or failure.
|
25
|
+
|
26
|
+
* [`DG::Combinators`](#combinators): a combinator library for building up
|
27
|
+
complex asynchronous operations out of simpler ones.
|
28
|
+
|
29
|
+
|
30
|
+
<h3 id="fluent"><tt>DG::Fluent</tt></h3>
|
31
|
+
|
32
|
+
Use JQuery-style fluent syntax for registering several callbacks and
|
33
|
+
errbacks on the same Deferrable. e.g.
|
34
|
+
|
35
|
+
DeferrableMonkeyShaver.new(monkey).
|
36
|
+
callback { puts "Monkey is shaved" }.
|
37
|
+
callback { monkey.entertain! }.
|
38
|
+
errback {|e| puts "Unable to shave monkey! #{e}" }.
|
39
|
+
errback {|_| monkey.terminate! }.
|
40
|
+
shave
|
41
|
+
|
42
|
+
<h3 id="bothback"><tt>DG::Bothback</tt></h3>
|
43
|
+
|
44
|
+
Register code to run on either success or failure: shorthand for calling both
|
45
|
+
`#callback` and `#errback` with the same code block.
|
46
|
+
|
47
|
+
<h3 id="combinators"><tt>DG::Combinators</tt></h3>
|
48
|
+
|
49
|
+
Allows building up higher-level asynchronous abstractions by composing simpler
|
50
|
+
asynchronous operations, without having to manually wire callbacks together
|
51
|
+
and remember to propagate errors correctly.
|
52
|
+
|
53
|
+
Motivating example: assume we have an asynchronous database API `DB.query`
|
54
|
+
which returns a Deferrable to communicate when the query finishes. (See
|
55
|
+
the [API docs for `DG::Combinators`](DeferrableGratification/Combinators.html)
|
56
|
+
for more detail.)
|
57
|
+
|
58
|
+
def product_names_for_username(username)
|
59
|
+
DB.query('SELECT id FROM users WHERE username = ?', username).bind! do |user_id|
|
60
|
+
DB.query('SELECT name FROM products WHERE user_id = ?', user_id)
|
61
|
+
end.map do |product_names|
|
62
|
+
product_names.join(', ')
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[deferrable_gratification bothback])
|
2
|
+
require File.join(File.dirname(__FILE__), *%w[deferrable_gratification combinators])
|
3
|
+
require File.join(File.dirname(__FILE__), *%w[deferrable_gratification default_deferrable])
|
4
|
+
require File.join(File.dirname(__FILE__), *%w[deferrable_gratification fluent])
|
5
|
+
require File.join(File.dirname(__FILE__), *%w[deferrable_gratification primitives])
|
6
|
+
|
7
|
+
# Deferrable Gratification ({DG}) makes evented code less error-prone and
|
8
|
+
# easier to compose, and thus easier to create higher-level abstractions
|
9
|
+
# around. It also enhances the API offered by Ruby Deferrables to make them
|
10
|
+
# more pleasant to work with.
|
11
|
+
#
|
12
|
+
# @see Fluent
|
13
|
+
# @see Bothback
|
14
|
+
# @see Combinators
|
15
|
+
module DeferrableGratification
|
16
|
+
# Allow DG.lift, DG.chain etc
|
17
|
+
extend Combinators::ClassMethods
|
18
|
+
|
19
|
+
# Allow DG.const, DG.failure etc
|
20
|
+
extend Primitives
|
21
|
+
|
22
|
+
# Bestow DG goodness upon an existing module or class.
|
23
|
+
#
|
24
|
+
# N.B. calling this on a module won't enhance any classes that have already
|
25
|
+
# included that module.
|
26
|
+
def self.enhance!(module_or_class)
|
27
|
+
module_or_class.send :include, Combinators
|
28
|
+
module_or_class.send :include, Fluent
|
29
|
+
module_or_class.send :include, Bothback
|
30
|
+
end
|
31
|
+
|
32
|
+
# Enhance EventMachine::Deferrable itself so that any class including it
|
33
|
+
# gets DG goodness. This should mean that all Deferrables in the current
|
34
|
+
# process will get enhanced.
|
35
|
+
#
|
36
|
+
# N.B. this will not enhance any classes that have *already* included
|
37
|
+
# Deferrable before this method was called, so you should call this before
|
38
|
+
# loading any other Deferrable libraries, and before defining any of your
|
39
|
+
# own Deferrable classes. (If that isn't possible, you can always call
|
40
|
+
# {enhance!} on them after definition.)
|
41
|
+
def self.enhance_all_deferrables!
|
42
|
+
require 'eventmachine'
|
43
|
+
require 'em/deferrable'
|
44
|
+
|
45
|
+
enhance! EventMachine::Deferrable
|
46
|
+
|
47
|
+
# Also have to do that to EM::DefaultDeferrable because it included
|
48
|
+
# Deferrable before we enhanced it.
|
49
|
+
enhance! EventMachine::DefaultDeferrable
|
50
|
+
end
|
51
|
+
|
52
|
+
# Make sure the combinator implementations themselves are enhanced. Have to
|
53
|
+
# do this here, rather than just including the modules in DefaultDeferrable,
|
54
|
+
# to avoid a nasty circular dependencies with default_deferrable.rb.
|
55
|
+
enhance! DefaultDeferrable
|
56
|
+
end
|
57
|
+
|
58
|
+
# Shorthand
|
59
|
+
DG = DeferrableGratification
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module DeferrableGratification
|
2
|
+
# Allows registering a 'bothback' that will be fired on either success or
|
3
|
+
# failure, analogous to the +ensure+ clause of a +begin+/+rescue+ block.
|
4
|
+
#
|
5
|
+
# Include this into a class that has already included +Deferrable+.
|
6
|
+
module Bothback
|
7
|
+
# Register +block+ to be called on either success or failure.
|
8
|
+
# This is just a shorthand for registering the same +block+ as both a
|
9
|
+
# callback and an errback.
|
10
|
+
#
|
11
|
+
# @return [Deferrable, Bothback] +self+
|
12
|
+
def bothback(&block)
|
13
|
+
callback(&block)
|
14
|
+
errback(&block)
|
15
|
+
self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
Dir.glob(File.join(File.dirname(__FILE__), *%w[combinators *.rb])) do |file|
|
2
|
+
require file.sub(/\.rb$/, '')
|
3
|
+
end
|
4
|
+
|
5
|
+
module DeferrableGratification
|
6
|
+
# Combinators for building up higher-level asynchronous abstractions by
|
7
|
+
# composing simpler asynchronous operations, without having to manually wire
|
8
|
+
# callbacks together and remember to propagate errors correctly.
|
9
|
+
#
|
10
|
+
# @example Perform a sequence of database queries and transform the result.
|
11
|
+
# # With DG::Combinators:
|
12
|
+
# def product_names_for_username(username)
|
13
|
+
# DB.query('SELECT id FROM users WHERE username = ?', username).bind! do |user_id|
|
14
|
+
# DB.query('SELECT name FROM products WHERE user_id = ?', user_id)
|
15
|
+
# end.map do |product_names|
|
16
|
+
# product_names.join(', ')
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# status = product_names_for_username('bob')
|
21
|
+
#
|
22
|
+
# status.callback {|product_names| ... }
|
23
|
+
# # If both queries complete successfully, the callback receives the string
|
24
|
+
# # "Car, Spoon, Coffee". The caller doesn't have to know that two separate
|
25
|
+
# # queries were made, or that the query result needed transforming into the
|
26
|
+
# # desired format: he just gets the event he cares about.
|
27
|
+
#
|
28
|
+
# status.errback {|error| puts "Oh no! #{error}" }
|
29
|
+
# # If either query went wrong, the errback receives the error that occurred.
|
30
|
+
#
|
31
|
+
#
|
32
|
+
# # Without DG::Combinators:
|
33
|
+
# def product_names_for_username(username)
|
34
|
+
# product_names_status = EM::DefaultDeferrable.new
|
35
|
+
# query1_status = DB.query('SELECT id FROM users WHERE username = ?', username)
|
36
|
+
# query1_status.callback do |user_id|
|
37
|
+
# query2_status = DB.query('SELECT name FROM products WHERE user_id = ?', user_id)
|
38
|
+
# query2_status.callback do |product_names|
|
39
|
+
# product_names = product_names.join(', ')
|
40
|
+
# # N.B. don't forget to report success to the caller!
|
41
|
+
# product_names_status.succeed(product_names)
|
42
|
+
# end
|
43
|
+
# query2_status.errback do |error|
|
44
|
+
# # N.B. don't forget to tell the caller we failed!
|
45
|
+
# product_names_status.fail(error)
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
# query1_status.errback do |error|
|
49
|
+
# # N.B. don't forget to tell the caller we failed!
|
50
|
+
# product_names_status.fail(error)
|
51
|
+
# end
|
52
|
+
# # oh yes, and don't forget to return this!
|
53
|
+
# product_names_status
|
54
|
+
# end
|
55
|
+
|
56
|
+
module Combinators
|
57
|
+
# Alias for {#bind!}.
|
58
|
+
#
|
59
|
+
# Note that this takes a +Proc+ (e.g. a lambda) while {#bind!} takes a
|
60
|
+
# block.
|
61
|
+
#
|
62
|
+
# @param [Proc] prok proc to call with the successful result of +self+.
|
63
|
+
# Assumed to return a Deferrable representing the status of its own
|
64
|
+
# operation.
|
65
|
+
#
|
66
|
+
# @return [Deferrable] status of the compound operation of passing the
|
67
|
+
# result of +self+ into the proc.
|
68
|
+
#
|
69
|
+
# @example Perform a database query that depends on the result of a previous query.
|
70
|
+
# DB.query('first query') >> lambda {|result| DB.query("query with #{result}") }
|
71
|
+
def >>(prok)
|
72
|
+
Bind.setup!(self, &prok)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Register callbacks so that when this Deferrable succeeds, its result
|
76
|
+
# will be passed to the block, which is assumed to return another
|
77
|
+
# Deferrable representing the status of a second operation.
|
78
|
+
#
|
79
|
+
# If this operation fails, the block will not be run. If either operation
|
80
|
+
# fails, the compound Deferrable returned will fire its errbacks, meaning
|
81
|
+
# callers don't have to know about the inner operations and can just
|
82
|
+
# subscribe to the result of {#bind!}.
|
83
|
+
#
|
84
|
+
#
|
85
|
+
# If you find yourself writing lots of nested {#bind!} calls, you can
|
86
|
+
# equivalently rewrite them as a chain and remove the nesting: e.g.
|
87
|
+
#
|
88
|
+
# a.bind! do |x|
|
89
|
+
# b(x).bind! do |y|
|
90
|
+
# c(y).bind! do |z|
|
91
|
+
# d(z)
|
92
|
+
# end
|
93
|
+
# end
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# has the same behaviour as
|
97
|
+
#
|
98
|
+
# a.bind! do |x|
|
99
|
+
# b(x)
|
100
|
+
# end.bind! do |y|
|
101
|
+
# c(y)
|
102
|
+
# end.bind! do |z|
|
103
|
+
# d(y)
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# As well as being more readable due to avoiding left margin inflation,
|
107
|
+
# this prevents introducing bugs due to inadvertent local variable capture
|
108
|
+
# by the nested blocks.
|
109
|
+
#
|
110
|
+
#
|
111
|
+
# @see #>>
|
112
|
+
#
|
113
|
+
# @param &block block to call with the successful result of +self+.
|
114
|
+
# Assumed to return a Deferrable representing the status of its own
|
115
|
+
# operation.
|
116
|
+
#
|
117
|
+
# @return [Deferrable] status of the compound operation of passing the
|
118
|
+
# result of +self+ into the block.
|
119
|
+
#
|
120
|
+
# @example Perform a web request based on the result of a database query.
|
121
|
+
# DB.query('url').bind! {|url| HTTP.get(url) }.
|
122
|
+
# callback {|response| puts "Got response!" }
|
123
|
+
def bind!(&block)
|
124
|
+
Bind.setup!(self, &block)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Transform the result of this Deferrable by invoking +block+, returning
|
128
|
+
# a Deferrable which succeeds with the transformed result.
|
129
|
+
#
|
130
|
+
# If this operation fails, the operation will not be run, and the returned
|
131
|
+
# Deferrable will also fail.
|
132
|
+
#
|
133
|
+
# @param &block block that transforms the expected result of this
|
134
|
+
# operation in some way.
|
135
|
+
#
|
136
|
+
# @return [Deferrable] Deferrable that will succeed if this operation did,
|
137
|
+
# after transforming its result.
|
138
|
+
#
|
139
|
+
# @example Retrieve a web page and call back with its title.
|
140
|
+
# HTTP.request(url).map {|page| Hpricot(page).at(:title).inner_html }
|
141
|
+
def map(&block)
|
142
|
+
bind!(&block)
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
# Boilerplate hook to extend {ClassMethods}.
|
147
|
+
def self.included(base)
|
148
|
+
base.send :extend, ClassMethods
|
149
|
+
end
|
150
|
+
|
151
|
+
# Combinators which don't make sense as methods on +Deferrable+.
|
152
|
+
#
|
153
|
+
# {DeferrableGratification} extends this module, and thus the methods
|
154
|
+
# here are accessible via the {DG} alias.
|
155
|
+
module ClassMethods
|
156
|
+
# Execute a sequence of asynchronous operations that may each depend on
|
157
|
+
# the result of the previous operation.
|
158
|
+
#
|
159
|
+
# @see #bind! more detail on the semantics.
|
160
|
+
#
|
161
|
+
# @param [*Proc] *actions procs that will perform an operation and
|
162
|
+
# return a Deferrable.
|
163
|
+
#
|
164
|
+
# @return [Deferrable] Deferrable that will succeed if all of the
|
165
|
+
# chained operations succeeded, and callback with the result of the
|
166
|
+
# last operation.
|
167
|
+
def chain(*actions)
|
168
|
+
actions.inject(DG.const(nil), &:>>)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[.. default_deferrable])
|
2
|
+
|
3
|
+
module DeferrableGratification
|
4
|
+
module Combinators
|
5
|
+
# Combinator that passes the result of one deferred operation to a block
|
6
|
+
# that uses that result to begin another deferred operation. The compound
|
7
|
+
# operation itself succeeds if the second operation does.
|
8
|
+
#
|
9
|
+
# You probably want to call {#bind!} rather than using this class directly.
|
10
|
+
#
|
11
|
+
# If we define +Deferrable err (IO a)+ to be the type of a Deferrable that
|
12
|
+
# may perform a side effect and then either succeed with a value of type a
|
13
|
+
# or fail with an error of type err, then we expect the block to have type
|
14
|
+
#
|
15
|
+
# a -> Deferrable err (IO b)
|
16
|
+
#
|
17
|
+
# and if so this combinator (actually {Bind.setup!}) is a specialisation
|
18
|
+
# of the monadic bind operator +>>=+:
|
19
|
+
#
|
20
|
+
# # example: database query that depends on the result of another
|
21
|
+
# Bind.setup!(DB.query('select foo from bar')) do |result|
|
22
|
+
# DB.query("select baz from quuz where name = '#{result}'")
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# # type signatures, in pseudo-Haskell
|
26
|
+
# (>>=) :: Deferrable err a ->
|
27
|
+
# (a -> Deferrable err b) -> Deferrable err b
|
28
|
+
# Bind :: Deferrable err a ->
|
29
|
+
# (a -> Deferrable err (IO b)) -> Deferrable err (IO b)
|
30
|
+
#
|
31
|
+
# However, because Ruby doesn't actually type-check blocks, we can't
|
32
|
+
# enforce that the block really does return a second Deferrable. This
|
33
|
+
# therefore also supports (reasonably) arbitrary blocks. However, it's
|
34
|
+
# probably clearer (though equivalent) to use {#map} for this case.
|
35
|
+
class Bind < DefaultDeferrable
|
36
|
+
# Prepare to bind +block+ to +first+, and create the Deferrable that
|
37
|
+
# will represent the bind.
|
38
|
+
#
|
39
|
+
# Does not actually set up any callbacks or errbacks: call {#setup!} for
|
40
|
+
# that.
|
41
|
+
#
|
42
|
+
# @param [Deferrable] first operation to bind to.
|
43
|
+
# @param &block block to run on success; should return a Deferrable.
|
44
|
+
#
|
45
|
+
# @raise [ArgumentError] if called without a block.
|
46
|
+
def initialize(first, &block)
|
47
|
+
@first = first
|
48
|
+
|
49
|
+
raise ArgumentError, 'must pass a block' unless block
|
50
|
+
@proc = block
|
51
|
+
end
|
52
|
+
|
53
|
+
# Register a callback on the first Deferrable to run the bound block on
|
54
|
+
# success, and an errback to fail this {Bind} on failure.
|
55
|
+
def setup!
|
56
|
+
@first.callback {|*args| run_bound_proc(*args) }
|
57
|
+
@first.errback {|*args| self.fail(*args) }
|
58
|
+
end
|
59
|
+
|
60
|
+
# Create a {Bind} and register the callbacks.
|
61
|
+
#
|
62
|
+
# @param (see #initialize)
|
63
|
+
#
|
64
|
+
# @return [Bind] Deferrable representing the compound operation.
|
65
|
+
#
|
66
|
+
# @raise (see #initialize)
|
67
|
+
def self.setup!(first, &block)
|
68
|
+
new(first, &block).tap(&:setup!)
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
def run_bound_proc(*args)
|
73
|
+
begin
|
74
|
+
second = @proc.call(*args)
|
75
|
+
rescue => error
|
76
|
+
self.fail(error)
|
77
|
+
else
|
78
|
+
# We expect the block to return a Deferrable, on which we can set
|
79
|
+
# callbacks. However, as referred to in the class comment above, we
|
80
|
+
# can't assume that it will, and need to behave sensibly if it
|
81
|
+
# doesn't.
|
82
|
+
if second.respond_to?(:callback) && second.respond_to?(:errback)
|
83
|
+
second.callback {|*args2| self.succeed(*args2) }
|
84
|
+
second.errback {|*error| self.fail(*error) }
|
85
|
+
else
|
86
|
+
# Not a Deferrable, so we need to "behave sensibly" as alluded to
|
87
|
+
# above. Just behaving like #map is sensible enough.
|
88
|
+
self.succeed(second)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'em/deferrable'
|
3
|
+
|
4
|
+
module DeferrableGratification
|
5
|
+
# Barebones Deferrable that includes the {DeferrableGratification}
|
6
|
+
# extensions:
|
7
|
+
#
|
8
|
+
# @see Fluent
|
9
|
+
# @see Bothback
|
10
|
+
# @see Combinators
|
11
|
+
class DefaultDeferrable
|
12
|
+
include EventMachine::Deferrable
|
13
|
+
|
14
|
+
# want to include Combinators here, but that would introduce a circular
|
15
|
+
# dependency, so have to do that in the top-level .rb file.
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module DeferrableGratification
|
2
|
+
# Allows JQuery-style fluent syntax for registering several callbacks and
|
3
|
+
# errbacks on the same Deferrable. e.g.
|
4
|
+
#
|
5
|
+
# DeferrableMonkeyShaver.new(monkey).
|
6
|
+
# callback { puts "Monkey is shaved" }.
|
7
|
+
# callback { monkey.entertain! }.
|
8
|
+
# errback {|e| puts "Unable to shave monkey! #{e}" }.
|
9
|
+
# errback {|_| monkey.terminate! }.
|
10
|
+
# shave
|
11
|
+
#
|
12
|
+
# Include this into a class that has already included +Deferrable+.
|
13
|
+
module Fluent
|
14
|
+
# Register +block+ to be called on success.
|
15
|
+
#
|
16
|
+
# @return [Deferrable, Fluent] +self+
|
17
|
+
#
|
18
|
+
# @see EventMachine::Deferrable#callback
|
19
|
+
def callback(&block)
|
20
|
+
super(&block)
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
# Register +block+ to be called on failure.
|
25
|
+
#
|
26
|
+
# @return [Deferrable, Fluent] +self+
|
27
|
+
#
|
28
|
+
# @see EventMachine::Deferrable#errback
|
29
|
+
def errback(&block)
|
30
|
+
super(&block)
|
31
|
+
self
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
Dir.glob(File.join(File.dirname(__FILE__), *%w[primitives *.rb])) do |file|
|
2
|
+
require file.sub(/\.rb$/, '')
|
3
|
+
end
|
4
|
+
|
5
|
+
module DeferrableGratification
|
6
|
+
# Trivial operations which return Deferrables.
|
7
|
+
#
|
8
|
+
# Used internally by the library, and may be useful in cases where you need
|
9
|
+
# to return a Deferrable to keep API compatibility but the result is already
|
10
|
+
# available.
|
11
|
+
#
|
12
|
+
# {DeferrableGratification} extends this module, and thus the methods here
|
13
|
+
# are accessible via the {DG} alias.
|
14
|
+
module Primitives
|
15
|
+
# Return a Deferrable which immediately succeeds with a constant value.
|
16
|
+
def const(value)
|
17
|
+
blank.tap {|d| d.succeed(value) }
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return a Deferrable which immediately fails with an exception.
|
21
|
+
def failure(class_or_message, message_or_nil = nil)
|
22
|
+
blank.tap do |d|
|
23
|
+
d.fail(
|
24
|
+
case class_or_message
|
25
|
+
when Class
|
26
|
+
class_or_message.new(message_or_nil)
|
27
|
+
else
|
28
|
+
RuntimeError.new(class_or_message.to_s)
|
29
|
+
end)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return a completely uninteresting Deferrable.
|
34
|
+
def blank
|
35
|
+
DeferrableGratification::DefaultDeferrable.new
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: deferrable_gratification
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Sam Stokes
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-01-15 00:00:00 -08:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: eventmachine
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rake
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: yard
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
type: :development
|
62
|
+
version_requirements: *id003
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: bluecloth
|
65
|
+
prerelease: false
|
66
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
type: :development
|
76
|
+
version_requirements: *id004
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
name: rspec
|
79
|
+
prerelease: false
|
80
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
hash: 3
|
86
|
+
segments:
|
87
|
+
- 2
|
88
|
+
- 3
|
89
|
+
- 0
|
90
|
+
version: 2.3.0
|
91
|
+
type: :development
|
92
|
+
version_requirements: *id005
|
93
|
+
description: |
|
94
|
+
Deferrable Gratification (DG) makes evented code less error-prone and easier to compose, and thus easier to create higher-level abstractions around. It also enhances the API offered by Ruby Deferrables to make them more pleasant to work with.
|
95
|
+
|
96
|
+
Currently consists of the following components:
|
97
|
+
|
98
|
+
* fluent (aka chainable) syntax for registering multiple callbacks and errbacks to the same Deferrable.
|
99
|
+
|
100
|
+
* a #bothback method for registering code to run on either success or failure.
|
101
|
+
|
102
|
+
* a combinator library for building up complex asynchronous operations out of simpler ones.
|
103
|
+
|
104
|
+
email:
|
105
|
+
- sam@rapportive.com
|
106
|
+
executables: []
|
107
|
+
|
108
|
+
extensions: []
|
109
|
+
|
110
|
+
extra_rdoc_files: []
|
111
|
+
|
112
|
+
files:
|
113
|
+
- lib/deferrable_gratification.rb
|
114
|
+
- lib/deferrable_gratification/default_deferrable.rb
|
115
|
+
- lib/deferrable_gratification/combinators/bind.rb
|
116
|
+
- lib/deferrable_gratification/combinators.rb
|
117
|
+
- lib/deferrable_gratification/bothback.rb
|
118
|
+
- lib/deferrable_gratification/fluent.rb
|
119
|
+
- lib/deferrable_gratification/primitives.rb
|
120
|
+
- LICENSE.MIT
|
121
|
+
- README.markdown
|
122
|
+
has_rdoc: true
|
123
|
+
homepage: http://github.com/samstokes/deferrable_gratification
|
124
|
+
licenses:
|
125
|
+
- MIT
|
126
|
+
post_install_message:
|
127
|
+
rdoc_options: []
|
128
|
+
|
129
|
+
require_paths:
|
130
|
+
- lib
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
132
|
+
none: false
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
hash: 57
|
137
|
+
segments:
|
138
|
+
- 1
|
139
|
+
- 8
|
140
|
+
- 7
|
141
|
+
version: 1.8.7
|
142
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
+
none: false
|
144
|
+
requirements:
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
hash: 3
|
148
|
+
segments:
|
149
|
+
- 0
|
150
|
+
version: "0"
|
151
|
+
requirements: []
|
152
|
+
|
153
|
+
rubyforge_project:
|
154
|
+
rubygems_version: 1.3.7
|
155
|
+
signing_key:
|
156
|
+
specification_version: 3
|
157
|
+
summary: Makes evented programming easier with composition and abstraction.
|
158
|
+
test_files: []
|
159
|
+
|