mr_darcy 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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +95 -0
- data/Guardfile +14 -0
- data/LICENSE.txt +22 -0
- data/README.md +267 -0
- data/Rakefile +6 -0
- data/lib/mr_darcy/context.rb +71 -0
- data/lib/mr_darcy/deferred.rb +25 -0
- data/lib/mr_darcy/drivers/celluloid.rb +23 -0
- data/lib/mr_darcy/drivers/synchronous.rb +15 -0
- data/lib/mr_darcy/drivers/thread.rb +22 -0
- data/lib/mr_darcy/drivers.rb +13 -0
- data/lib/mr_darcy/promise/base.rb +117 -0
- data/lib/mr_darcy/promise/celluloid.rb +52 -0
- data/lib/mr_darcy/promise/child_promise.rb +83 -0
- data/lib/mr_darcy/promise/dsl.rb +29 -0
- data/lib/mr_darcy/promise/em.rb +29 -0
- data/lib/mr_darcy/promise/state/base.rb +43 -0
- data/lib/mr_darcy/promise/state/rejected.rb +11 -0
- data/lib/mr_darcy/promise/state/resolved.rb +11 -0
- data/lib/mr_darcy/promise/state/unresolved.rb +19 -0
- data/lib/mr_darcy/promise/state.rb +25 -0
- data/lib/mr_darcy/promise/synchronous.rb +27 -0
- data/lib/mr_darcy/promise/thread.rb +64 -0
- data/lib/mr_darcy/promise.rb +31 -0
- data/lib/mr_darcy/role.rb +45 -0
- data/lib/mr_darcy/version.rb +3 -0
- data/lib/mr_darcy.rb +26 -0
- data/mr_darcy.gemspec +29 -0
- data/spec/acceptance/dci_bank_transfer_spec.rb +62 -0
- data/spec/acceptance/em_http_request_spec.rb +22 -0
- data/spec/acceptance/open-uri_http_request_spec.rb +21 -0
- data/spec/acceptance/simple_promise_spec.rb +25 -0
- data/spec/acceptance/simple_promise_with_chained_fail.rb +25 -0
- data/spec/acceptance/simple_promise_with_fail_spec.rb +25 -0
- data/spec/acceptance/simple_promise_with_then_spec.rb +25 -0
- data/spec/lib/mr_darcy/context_spec.rb +22 -0
- data/spec/lib/mr_darcy/promise/base_spec.rb +197 -0
- data/spec/lib/mr_darcy/promise/child_promise_spec.rb +169 -0
- data/spec/lib/mr_darcy/promise/dsl_spec.rb +43 -0
- data/spec/lib/mr_darcy/promise/state/base_spec.rb +24 -0
- data/spec/lib/mr_darcy/promise/state/rejected_spec.rb +12 -0
- data/spec/lib/mr_darcy/promise/state/resolved_spec.rb +12 -0
- data/spec/lib/mr_darcy/promise/state/unresolved_spec.rb +22 -0
- data/spec/lib/mr_darcy/promise/state_spec.rb +30 -0
- data/spec/lib/mr_darcy/promise/synchronous_spec.rb +21 -0
- data/spec/lib/mr_darcy/promise_spec.rb +72 -0
- data/spec/lib/mr_darcy/role_spec.rb +89 -0
- data/spec/lib/mr_darcy_spec.rb +19 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/context_helpers.rb +19 -0
- data/spec/support/shared_examples_for_promise.rb +47 -0
- data/spec/support/shared_examples_for_thennable.rb +10 -0
- metadata +279 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 200c16f28a04c2608e104d084400e44c55f84e78
|
4
|
+
data.tar.gz: 2c4132617b62c2713cedcc37d560c55a180e6251
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: baa8c75ad0f00c458e6610b389a52b29d345b705d02f71ca845a8df4297deb31c3c7c5b6b556ef3338b179a807f8e5f53121308d30eb0111f045bdbf6f1bd044
|
7
|
+
data.tar.gz: c73abc19ded59554f375cbf4e34a74fa946e114d2a34d90711e568f0b47179b5535675fb6448614706024108b33ef27cc41f701347a086d9943bf4476a0970e6
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
mr_darcy (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
addressable (2.3.5)
|
10
|
+
byebug (2.6.0)
|
11
|
+
columnize (~> 0.3)
|
12
|
+
debugger-linecache (~> 1.2)
|
13
|
+
celluloid (0.15.2)
|
14
|
+
timers (~> 1.1.0)
|
15
|
+
celluloid-io (0.15.0)
|
16
|
+
celluloid (>= 0.15.0)
|
17
|
+
nio4r (>= 0.5.0)
|
18
|
+
coderay (1.1.0)
|
19
|
+
columnize (0.3.6)
|
20
|
+
cookiejar (0.3.0)
|
21
|
+
debugger-linecache (1.2.0)
|
22
|
+
diff-lcs (1.2.5)
|
23
|
+
em-http-request (1.0.3)
|
24
|
+
addressable (>= 2.2.3)
|
25
|
+
cookiejar
|
26
|
+
em-socksify
|
27
|
+
eventmachine (>= 1.0.0.beta.4)
|
28
|
+
http_parser.rb (>= 0.5.3)
|
29
|
+
em-socksify (0.2.1)
|
30
|
+
eventmachine (>= 1.0.0.beta.4)
|
31
|
+
eventmachine (1.0.3)
|
32
|
+
ffi (1.9.3)
|
33
|
+
formatador (0.2.4)
|
34
|
+
guard (2.4.0)
|
35
|
+
formatador (>= 0.2.4)
|
36
|
+
listen (~> 2.1)
|
37
|
+
lumberjack (~> 1.0)
|
38
|
+
pry (>= 0.9.12)
|
39
|
+
thor (>= 0.18.1)
|
40
|
+
guard-bundler (2.0.0)
|
41
|
+
bundler (~> 1.0)
|
42
|
+
guard (~> 2.2)
|
43
|
+
guard-rspec (4.2.6)
|
44
|
+
guard (~> 2.1)
|
45
|
+
rspec (>= 2.14, < 4.0)
|
46
|
+
http_parser.rb (0.6.0)
|
47
|
+
listen (2.5.0)
|
48
|
+
celluloid (>= 0.15.2)
|
49
|
+
celluloid-io (>= 0.15.0)
|
50
|
+
rb-fsevent (>= 0.9.3)
|
51
|
+
rb-inotify (>= 0.9)
|
52
|
+
lumberjack (1.0.4)
|
53
|
+
method_source (0.8.2)
|
54
|
+
nio4r (1.0.0)
|
55
|
+
pry (0.9.12.6)
|
56
|
+
coderay (~> 1.0)
|
57
|
+
method_source (~> 0.8)
|
58
|
+
slop (~> 3.4)
|
59
|
+
pry-byebug (1.3.1)
|
60
|
+
byebug (~> 2.6)
|
61
|
+
pry (~> 0.9.12)
|
62
|
+
rake (10.1.1)
|
63
|
+
rb-fsevent (0.9.4)
|
64
|
+
rb-inotify (0.9.3)
|
65
|
+
ffi (>= 0.5.0)
|
66
|
+
rspec (2.14.1)
|
67
|
+
rspec-core (~> 2.14.0)
|
68
|
+
rspec-expectations (~> 2.14.0)
|
69
|
+
rspec-mocks (~> 2.14.0)
|
70
|
+
rspec-core (2.14.7)
|
71
|
+
rspec-expectations (2.14.5)
|
72
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
73
|
+
rspec-mocks (2.14.5)
|
74
|
+
slop (3.4.7)
|
75
|
+
terminal-notifier-guard (1.5.3)
|
76
|
+
thor (0.18.1)
|
77
|
+
timers (1.1.0)
|
78
|
+
|
79
|
+
PLATFORMS
|
80
|
+
ruby
|
81
|
+
|
82
|
+
DEPENDENCIES
|
83
|
+
bundler (~> 1.5)
|
84
|
+
celluloid
|
85
|
+
em-http-request
|
86
|
+
eventmachine
|
87
|
+
guard
|
88
|
+
guard-bundler
|
89
|
+
guard-rspec
|
90
|
+
mr_darcy!
|
91
|
+
pry
|
92
|
+
pry-byebug
|
93
|
+
rake
|
94
|
+
rspec
|
95
|
+
terminal-notifier-guard
|
data/Guardfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard :bundler do
|
5
|
+
watch('Gemfile')
|
6
|
+
watch(/^.+\.gemspec/)
|
7
|
+
end
|
8
|
+
|
9
|
+
guard :rspec do
|
10
|
+
watch(%r{^spec/.+_spec\.rb$})
|
11
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
12
|
+
watch('spec/spec_helper.rb') { "spec" }
|
13
|
+
end
|
14
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 James Harton
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,267 @@
|
|
1
|
+
# MrDarcy
|
2
|
+
|
3
|
+
A mashup of Async Promises and DCI in ruby.
|
4
|
+
|
5
|
+

|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'mr_darcy'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
```sh
|
18
|
+
bundle
|
19
|
+
```
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
```sh
|
24
|
+
gem install mr_darcy
|
25
|
+
```
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
### WARNING
|
30
|
+
|
31
|
+
MrDarcy is definitely experimental, and was mostly built over the weekend of
|
32
|
+
[RailsCamp NZ 5](http://camp.ruby.org.nz/) with the generous help of the
|
33
|
+
amazing and sexy [@breccan](https://twitter.com/breccan) and
|
34
|
+
[@eoinkelly](https://twitter.com/eoinkelly).
|
35
|
+
|
36
|
+
#### Should I use MrDarcy in Production?
|
37
|
+
|
38
|
+
No.
|
39
|
+
|
40
|
+
#### How can I help make MrDarcy production ready?
|
41
|
+
|
42
|
+
Run it in production. Report bugs.
|
43
|
+
|
44
|
+
### Such promise. Many then.
|
45
|
+
|
46
|
+
Promises are a way of structuring batches of (async) functionality into a
|
47
|
+
pipeline, in such a way as to make them seem synchronous.
|
48
|
+
|
49
|
+
Here's an example:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
# We're going to wrap an asynchronous web request using EventMachine
|
53
|
+
# in a promise:
|
54
|
+
data = MrDarcy.promise do
|
55
|
+
EM.run do
|
56
|
+
http = EM.HttpRequest.new('http://camp.ruby.org.nz/').get
|
57
|
+
http.errback do
|
58
|
+
reject http.error
|
59
|
+
EM.stop
|
60
|
+
end
|
61
|
+
http.callback do
|
62
|
+
resolve http.response
|
63
|
+
EM.stop
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end.then do |response|
|
67
|
+
response.body
|
68
|
+
end.result
|
69
|
+
|
70
|
+
puts data
|
71
|
+
```
|
72
|
+
|
73
|
+
What's cool about MrDarcy is that we can switch between different methods of
|
74
|
+
doing async ruby:
|
75
|
+
- Naive threads, using MRI's thread implementation.
|
76
|
+
- Reactor pattern, using [EventMachine](http://rubyeventmachine.com/) to
|
77
|
+
schedule promises on the a reactor thread.
|
78
|
+
- Actor pattern, using [Celluloid](http://celluloid.io/) to schedule
|
79
|
+
promises using Celluloid futures.
|
80
|
+
|
81
|
+
#### Key points to know about Promises
|
82
|
+
|
83
|
+
1. You create them with a block, which is scheduled asynchronously, and
|
84
|
+
inside of which you can place your long-running executable. Inside this
|
85
|
+
block you call either `resolve <value>` or `reject <exception>` to resolve
|
86
|
+
or reject the promise.
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
MrDarcy.promise do
|
90
|
+
accellerate_the_delorean
|
91
|
+
if speed >= 88
|
92
|
+
resolve :time_flux_initiated
|
93
|
+
else
|
94
|
+
reject :engage_service_brake
|
95
|
+
end
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
2. All promises have `then` and `fail` methods, to which you pass a block to
|
100
|
+
be called when the promise resolves (`then`) or rejects (`fail`). These
|
101
|
+
methods return new promises, upon which you can chain more `then` and
|
102
|
+
`fail` calls.
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
MrDarcy.promise do
|
106
|
+
i = rand
|
107
|
+
i > 0.5 ? resolve i : reject i
|
108
|
+
end.then |value|
|
109
|
+
# success
|
110
|
+
end.fail |value|
|
111
|
+
# failure
|
112
|
+
end
|
113
|
+
```
|
114
|
+
|
115
|
+
3. `fail` is used to catch errors asynchronously, and deal with them.
|
116
|
+
Therefore the result of a `fail` block will be a resolved promise.
|
117
|
+
If you wish to keep processing a failure then you can `raise` it
|
118
|
+
within the `fail` block to pass it along to the next `fail` block.
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
MrDarcy.promise do
|
122
|
+
reject 2
|
123
|
+
end.fail |value|
|
124
|
+
value * value
|
125
|
+
end.then |value|
|
126
|
+
# I am called with 4
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
4. Failures cascade until they're caught:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
MrDarcy.promise do
|
134
|
+
reject :fail
|
135
|
+
end.then
|
136
|
+
# I am skipped
|
137
|
+
end.then
|
138
|
+
# as am I
|
139
|
+
end.fail
|
140
|
+
# I am called
|
141
|
+
end
|
142
|
+
```
|
143
|
+
|
144
|
+
5. If your block returns a new promise, then `then` or `fail` will defer
|
145
|
+
their resolution until the new promise is resolved:
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
MrDarcy.promise do
|
149
|
+
resolve 1
|
150
|
+
end.then do |value|
|
151
|
+
MrDarcy.promise do
|
152
|
+
resolve value * 2
|
153
|
+
end
|
154
|
+
end.then |value|
|
155
|
+
# I will be called with 2
|
156
|
+
end
|
157
|
+
```
|
158
|
+
|
159
|
+
### Sprinkle on some DCI goodness.
|
160
|
+
|
161
|
+
[DCI](http://fulloo.info) is a method of specifying interactions between
|
162
|
+
objects in a single location, by decorating or extending your data objects
|
163
|
+
within a context, running the interaction and then (optionally) removing
|
164
|
+
the extensions again.
|
165
|
+
|
166
|
+
Other takes on DCI in Ruby:
|
167
|
+
- [playhouse](https://github.com/enspiral/playhouse) is an app framework
|
168
|
+
for building entire apps with DCI from the lovable hippies at
|
169
|
+
[enspiral](http://www.enspiral.com/),
|
170
|
+
|
171
|
+
- [surrounded](https://github.com/saturnflyer/surrounded) by
|
172
|
+
[Jim Gay](https://github.com/saturnflyer) is a gem for doing DCI in
|
173
|
+
a simple, repeatable fashion.
|
174
|
+
|
175
|
+
MrDarcy is a little differnt to these approaches, as is builds an interesting
|
176
|
+
DSL on top of promises to create contexts that are alive as long as they need
|
177
|
+
to be to achieve their goal, even when code is being run asynchronously.
|
178
|
+
|
179
|
+
Here's how we define a classic bank trasfer example:
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
class BankTransfer < MrDarcy::Context
|
183
|
+
role :money_source do
|
184
|
+
def has_available_funds? amount
|
185
|
+
available_balance >= amount
|
186
|
+
end
|
187
|
+
|
188
|
+
def subtract_funds amount
|
189
|
+
self.available_balance = available_balance - amount
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
role :money_destination do
|
194
|
+
def receive_funds amount
|
195
|
+
self.available_balance = available_balance + amount
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
action :transfer do |amount|
|
200
|
+
if money_source.has_available_funds? amount
|
201
|
+
money_source.subtract_funds amount
|
202
|
+
money_destination.receive_funds amount
|
203
|
+
else
|
204
|
+
raise "insufficient funds"
|
205
|
+
end
|
206
|
+
amount
|
207
|
+
end
|
208
|
+
end
|
209
|
+
```
|
210
|
+
|
211
|
+
- The `role` class method defines roles, which the context will expect to be
|
212
|
+
passed on object creation. They also define the extra behaviour (methods)
|
213
|
+
that we wish to see added to our role players at initialisation.
|
214
|
+
|
215
|
+
- The `action` class method defines our interactions. In other words, this
|
216
|
+
is where we define how the interaction will take place. These also define
|
217
|
+
instance methods on the class of the same name, which return a promise
|
218
|
+
when called.
|
219
|
+
|
220
|
+
- You can have as many roles and actions as needed by your context.
|
221
|
+
|
222
|
+
Let's transfer some funds between two accounts:
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
Account = Struct.new(:available_balance)
|
226
|
+
marty = Account.new(10)
|
227
|
+
doc_brown = Account.new(15)
|
228
|
+
|
229
|
+
context = BankTransfer.new money_source: marty, money_destination: doc_brown
|
230
|
+
context.transfer(5).then do |amount|
|
231
|
+
puts "Successfully transferred #{amount} from #{money_source} to #{money_destination}"
|
232
|
+
end
|
233
|
+
|
234
|
+
context.transfer(50).fail do |exception|
|
235
|
+
puts "Failed to transfer funds: #{exception.message}"
|
236
|
+
end
|
237
|
+
```
|
238
|
+
|
239
|
+
What's super cool, however is that because promises can return and chain other
|
240
|
+
promises, we can come up with some pretty involved scenarios:
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
marty = Account.new(10)
|
244
|
+
jenn = Account.new(10)
|
245
|
+
doc_brown = Account.new(200)
|
246
|
+
|
247
|
+
context = BankTransfer.new money_source: marty, money_destination: jenn
|
248
|
+
context.transfer(20).fail do
|
249
|
+
# Oh no, Marty doesn't have enough money, let's borrow some from Doc.
|
250
|
+
BankTransfer.new(money_source: doc_brown, money_destination: marty) \
|
251
|
+
.transfer(20).then
|
252
|
+
# Thy transferring again.
|
253
|
+
context.transfer(20)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
```
|
257
|
+
|
258
|
+
I hope that's enough to get you started. Yup, it's a bit crazy, but it might
|
259
|
+
just work.
|
260
|
+
|
261
|
+
## Contributing
|
262
|
+
|
263
|
+
1. Fork it ( http://github.com/<my-github-username>/mr_darcy/fork )
|
264
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
265
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
266
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
267
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
module MrDarcy
|
2
|
+
class Context
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def role role_name, options={}, &block
|
6
|
+
self.roles[role_name] = Role.new(role_name, options, &block)
|
7
|
+
end
|
8
|
+
|
9
|
+
def action action_name, &block
|
10
|
+
define_method action_name do |*args|
|
11
|
+
self.then do |value|
|
12
|
+
self.instance_exec(*args, &block)
|
13
|
+
end
|
14
|
+
self
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def roles
|
19
|
+
@roles ||= {}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize role_players={}
|
24
|
+
@driver = role_players.delete(:driver) || MrDarcy.driver
|
25
|
+
@deferred = Deferred.new(driver: driver) {}
|
26
|
+
deferred.resolve nil
|
27
|
+
|
28
|
+
roles = self.class.roles
|
29
|
+
roles.each do |role_name, role|
|
30
|
+
player = role_players[role_name]
|
31
|
+
raise ArgumentError, "No role player for #{role_name} supplied" unless player
|
32
|
+
|
33
|
+
role.pollute(player)
|
34
|
+
|
35
|
+
self.singleton_class.send :define_method, role_name do
|
36
|
+
player
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def then &block
|
42
|
+
deferred.then do |value|
|
43
|
+
self.instance_exec(value, &block)
|
44
|
+
end
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def fail &block
|
49
|
+
deferred.fail do |value|
|
50
|
+
self.instance_exec(value, &block)
|
51
|
+
end
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
%w| result rejected? resolved? unresolved? |.map(&:to_sym).each do |method|
|
56
|
+
define_method method do
|
57
|
+
deferred.public_send method
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def final
|
62
|
+
deferred.final
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
attr_accessor :deferred, :driver
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module MrDarcy
|
2
|
+
class Deferred
|
3
|
+
|
4
|
+
attr_accessor :promise, :last_promise
|
5
|
+
|
6
|
+
%w| resolved? rejected? unresolved? resolve reject final result |.map(&:to_sym).each do |method|
|
7
|
+
define_method method do |*args|
|
8
|
+
promise.public_send method, *args
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def then &block
|
13
|
+
self.last_promise = last_promise.then(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def fail &block
|
17
|
+
self.last_promise = last_promise.fail(&block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize driver: MrDarcy.driver
|
21
|
+
self.promise = MrDarcy::Promise.new(driver: driver) {}
|
22
|
+
self.last_promise = promise
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'celluloid/autostart'
|
2
|
+
|
3
|
+
module MrDarcy
|
4
|
+
module Drivers
|
5
|
+
module Celluloid
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def dispatch(&block)
|
9
|
+
futures << ::Celluloid::Future.new(&block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def wait
|
13
|
+
futures.each do |future|
|
14
|
+
future.value
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def futures
|
19
|
+
@futures ||= []
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module MrDarcy
|
4
|
+
module Drivers
|
5
|
+
module Thread
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def dispatch(&block)
|
9
|
+
@threads ||= []
|
10
|
+
@threads << ::Thread.new(&block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def wait
|
14
|
+
@threads ||= []
|
15
|
+
@threads.each do |thread|
|
16
|
+
thread.join unless ::Thread.current == thread
|
17
|
+
end
|
18
|
+
yield
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module MrDarcy
|
2
|
+
module Drivers
|
3
|
+
autoload :Synchronous, File.expand_path('../drivers/synchronous.rb', __FILE__)
|
4
|
+
autoload :Thread, File.expand_path('../drivers/thread.rb', __FILE__)
|
5
|
+
autoload :Celluloid, File.expand_path('../drivers/celluloid.rb', __FILE__)
|
6
|
+
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def all
|
10
|
+
[ Synchronous, Thread, Celluloid ]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|