deferrable_gratification 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|