volt 0.9.5.pre3 → 0.9.5.pre4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2 -0
- data/app/volt/models/active_volt_instance.rb +6 -0
- data/app/volt/tasks/live_query/live_query.rb +2 -2
- data/app/volt/tasks/query_tasks.rb +1 -1
- data/lib/volt/models.rb +9 -11
- data/lib/volt/models/array_model.rb +80 -36
- data/lib/volt/models/field_helpers.rb +5 -1
- data/lib/volt/models/permissions.rb +81 -72
- data/lib/volt/models/persistors/array_store.rb +5 -25
- data/lib/volt/models/root_models/store_root.rb +0 -1
- data/lib/volt/reactive/reactive_array.rb +4 -1
- data/lib/volt/server/message_bus/peer_to_peer.rb +2 -2
- data/lib/volt/server/message_bus/peer_to_peer/server_tracker.rb +1 -1
- data/lib/volt/server/middleware/default_middleware_stack.rb +3 -2
- data/lib/volt/server/rack/asset_files.rb +18 -0
- data/lib/volt/server/rack/component_code.rb +1 -1
- data/lib/volt/server/rack/index_files.rb +2 -2
- data/lib/volt/server/rack/opal_files.rb +3 -0
- data/lib/volt/spec/setup.rb +0 -2
- data/lib/volt/utils/promise_extensions.rb +4 -3
- data/lib/volt/version.rb +1 -1
- data/lib/volt/volt/app.rb +5 -0
- data/lib/volt/volt/users.rb +2 -2
- data/spec/extra_core/class_spec.rb +11 -0
- data/spec/integration/bindings_spec.rb +0 -1
- data/spec/models/array_model_spec.rb +16 -0
- data/spec/models/field_helpers_spec.rb +8 -1
- data/spec/models/permissions_spec.rb +25 -0
- data/volt.gemspec +1 -1
- metadata +5 -5
- data/lib/volt/utils/promise.rb +0 -429
@@ -168,43 +168,23 @@ module Volt
|
|
168
168
|
end
|
169
169
|
|
170
170
|
# Call a method on the model once the model is loaded. Return a promise
|
171
|
-
# that will resolve
|
172
|
-
|
173
|
-
def run_once_loaded(block)
|
171
|
+
# that will resolve when the model is loaded
|
172
|
+
def run_once_loaded
|
174
173
|
promise = Promise.new
|
175
174
|
|
176
|
-
# call once the method is loaded.
|
177
|
-
model_loaded = proc do
|
178
|
-
begin
|
179
|
-
result = yield
|
180
|
-
promise.resolve(result)
|
181
|
-
rescue Exception => error
|
182
|
-
promise.reject(error)
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
# Run the block after resolve if a block is passed in
|
187
|
-
if block
|
188
|
-
promise2 = promise.then do |val|
|
189
|
-
block.call(val)
|
190
|
-
end
|
191
|
-
else
|
192
|
-
promise2 = promise
|
193
|
-
end
|
194
|
-
|
195
175
|
if @model.loaded_state == :loaded
|
196
|
-
|
176
|
+
promise.resolve(nil)
|
197
177
|
else
|
198
178
|
proc do |comp|
|
199
179
|
if @model.loaded_state == :loaded
|
200
|
-
|
180
|
+
promise.resolve(nil)
|
201
181
|
|
202
182
|
comp.stop
|
203
183
|
end
|
204
184
|
end.watch!
|
205
185
|
end
|
206
186
|
|
207
|
-
|
187
|
+
promise
|
208
188
|
end
|
209
189
|
|
210
190
|
# Returns a promise that is resolved/rejected when the query is complete. Any
|
@@ -11,13 +11,16 @@ module Volt
|
|
11
11
|
@old_size = 0
|
12
12
|
end
|
13
13
|
|
14
|
+
def respond_to_missing?(method_name, include_private = false)
|
15
|
+
@array.respond_to?(method_name, include_private) || super
|
16
|
+
end
|
17
|
+
|
14
18
|
# Forward any missing methods to the array
|
15
19
|
def method_missing(method_name, *args, &block)
|
16
20
|
# Long term we should probably handle all Enum methods
|
17
21
|
# directly for smarter updating. For now, just depend on size
|
18
22
|
# so updates retrigger the whole method call.
|
19
23
|
@size_dep.depend
|
20
|
-
|
21
24
|
@array.send(method_name, *args, &block)
|
22
25
|
end
|
23
26
|
|
@@ -77,7 +77,7 @@ module Volt
|
|
77
77
|
connect_to_peers
|
78
78
|
end
|
79
79
|
else
|
80
|
-
Volt.logger.error('Unable to connect to the database. Volt will still run, but the message bus requires a database connection to setup connections between nodes, so the message bus has been disabled. This means updates will not be propagated between instances (server, console, runners, etc...)')
|
80
|
+
Volt.logger.error('Unable to connect to the database. Currently Volt requires running mongodb for a few things to work. Volt will still run, but the message bus requires a database connection to setup connections between nodes, so the message bus has been disabled. Also, the store collection can not be used without a database. This means updates will not be propagated between instances (server, console, runners, etc...)')
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
@@ -181,7 +181,7 @@ module Volt
|
|
181
181
|
def still_alive?(peer_server_id)
|
182
182
|
# Unable to write to the socket, retry until the instance is no
|
183
183
|
# longer marking its self as active in the database
|
184
|
-
peer_table = @volt_app.store.
|
184
|
+
peer_table = @volt_app.store.active_volt_instances
|
185
185
|
peer = peer_table.where(server_id: peer_server_id).first.sync
|
186
186
|
if peer
|
187
187
|
# Found the peer, retry if it has reported in in the last 2
|
@@ -33,7 +33,7 @@ module Volt
|
|
33
33
|
|
34
34
|
# Register this server as active with the database
|
35
35
|
def register
|
36
|
-
instances = @volt_app.store.
|
36
|
+
instances = @volt_app.store.active_volt_instances
|
37
37
|
instances.where(server_id: @server_id).first.then do |item|
|
38
38
|
ips = local_ips.join(',')
|
39
39
|
time = Time.now.to_i
|
@@ -50,10 +50,11 @@ module Volt
|
|
50
50
|
|
51
51
|
rack_app.use HttpResource, volt_app, volt_app.router
|
52
52
|
|
53
|
+
# serve assets from public
|
53
54
|
rack_app.use Rack::Static,
|
54
55
|
urls: ['/'],
|
55
|
-
root: '
|
56
|
-
index: '',
|
56
|
+
root: 'public',
|
57
|
+
index: 'index.html',
|
57
58
|
header_rules: [
|
58
59
|
[:all, { 'Cache-Control' => 'public, max-age=86400' }]
|
59
60
|
]
|
@@ -4,6 +4,16 @@ require 'uri'
|
|
4
4
|
# from the dependencies.rb files.
|
5
5
|
module Volt
|
6
6
|
class AssetFiles
|
7
|
+
def self.from_cache(component_name, component_paths)
|
8
|
+
# @cache ||= {}
|
9
|
+
|
10
|
+
# @cache[component_name] ||= begin
|
11
|
+
# not cached, create
|
12
|
+
|
13
|
+
self.new(component_name, component_paths)
|
14
|
+
# end
|
15
|
+
end
|
16
|
+
|
7
17
|
def initialize(component_name, component_paths)
|
8
18
|
@component_paths = component_paths
|
9
19
|
@assets = []
|
@@ -61,6 +71,14 @@ module Volt
|
|
61
71
|
end
|
62
72
|
end
|
63
73
|
|
74
|
+
# Called when you want to add a gem to the opal load path so it can be
|
75
|
+
# required on the client side.
|
76
|
+
def opal_gem(gem_name)
|
77
|
+
Opal.use_gem(gem_name)
|
78
|
+
Opal.paths.uniq!
|
79
|
+
# require(gem_name)
|
80
|
+
end
|
81
|
+
|
64
82
|
def components
|
65
83
|
@included_components.keys
|
66
84
|
end
|
@@ -17,7 +17,7 @@ module Volt
|
|
17
17
|
# Start with config code
|
18
18
|
code = @client ? generate_config_code : ''
|
19
19
|
|
20
|
-
asset_files = AssetFiles.
|
20
|
+
asset_files = AssetFiles.from_cache(@component_name, @component_paths)
|
21
21
|
asset_files.component_paths.each do |component_path, component_name|
|
22
22
|
code << ComponentTemplates.new(component_path, component_name, @client).code
|
23
23
|
code << "\n\n"
|
@@ -58,11 +58,11 @@ module Volt
|
|
58
58
|
|
59
59
|
def javascript_tags
|
60
60
|
# TODO: Cache somehow, this is being loaded every time
|
61
|
-
AssetFiles.
|
61
|
+
AssetFiles.from_cache('main', @component_paths).javascript_tags(@volt_app)
|
62
62
|
end
|
63
63
|
|
64
64
|
def css_tags
|
65
|
-
AssetFiles.
|
65
|
+
AssetFiles.from_cache('main', @component_paths).css_tags
|
66
66
|
end
|
67
67
|
end
|
68
68
|
end
|
data/lib/volt/spec/setup.rb
CHANGED
@@ -1,6 +1,3 @@
|
|
1
|
-
# Require the original promise library first.
|
2
|
-
require 'volt/utils/promise'
|
3
|
-
|
4
1
|
# A temp patch for promises until https://github.com/opal/opal/pull/725 is released.
|
5
2
|
class Promise
|
6
3
|
class UnrealizedPromiseException < RuntimeError ; end
|
@@ -113,6 +110,10 @@ class Promise
|
|
113
110
|
end
|
114
111
|
|
115
112
|
if error
|
113
|
+
if error.is_a?(RSpec::Expectations::ExpectationNotMetError)
|
114
|
+
# re-raise
|
115
|
+
raise error
|
116
|
+
end
|
116
117
|
err_str = "Exception in Promise at .sync: #{error.inspect}"
|
117
118
|
err_str += error.backtrace.join("\n") if error.respond_to?(:backtrace)
|
118
119
|
Volt.logger.error(err_str)
|
data/lib/volt/version.rb
CHANGED
data/lib/volt/volt/app.rb
CHANGED
@@ -88,6 +88,11 @@ module Volt
|
|
88
88
|
|
89
89
|
load_app_code
|
90
90
|
|
91
|
+
# Load up the main component dependencies. This is needed to load in
|
92
|
+
# any opal_gem calls in dependencies.rb
|
93
|
+
# TODO: Needs to support all components
|
94
|
+
AssetFiles.from_cache('main', component_paths)
|
95
|
+
|
91
96
|
reset_query_pool!
|
92
97
|
|
93
98
|
# Setup the middleware that we can only setup after all components boot.
|
data/lib/volt/volt/users.rb
CHANGED
@@ -93,12 +93,12 @@ module Volt
|
|
93
93
|
|
94
94
|
# Put in a deprecation placeholder
|
95
95
|
def user
|
96
|
-
Volt.logger.warn('
|
96
|
+
Volt.logger.warn('Deprecation: Volt.user has been renamed to Volt.current_user (to be more clear about what it returns). Volt.user will be deprecated in the future.')
|
97
97
|
current_user
|
98
98
|
end
|
99
99
|
|
100
100
|
def fetch_current_user
|
101
|
-
Volt.logger.warn("
|
101
|
+
Volt.logger.warn("Deprecation Warning: fetch current user have been depricated, Volt.current_user returns a promise now.")
|
102
102
|
current_user
|
103
103
|
end
|
104
104
|
|
@@ -3,6 +3,7 @@ require 'volt/extra_core/array'
|
|
3
3
|
|
4
4
|
class TestClassAttributes
|
5
5
|
class_attribute :some_data
|
6
|
+
class_attribute :attr2
|
6
7
|
end
|
7
8
|
|
8
9
|
class TestSubClassAttributes < TestClassAttributes
|
@@ -30,4 +31,14 @@ describe 'extra_core class addons' do
|
|
30
31
|
expect(TestSubClassAttributes.some_data).to eq(10)
|
31
32
|
expect(TestSubClassAttributes2.some_data).to eq(15)
|
32
33
|
end
|
34
|
+
|
35
|
+
it 'should let you change a class attribute on the child without affecting the parent' do
|
36
|
+
TestClassAttributes.attr2 = 1
|
37
|
+
expect(TestSubClassAttributes.attr2).to eq(1)
|
38
|
+
|
39
|
+
TestSubClassAttributes.attr2 = 2
|
40
|
+
expect(TestClassAttributes.attr2).to eq(1)
|
41
|
+
expect(TestSubClassAttributes.attr2).to eq(2)
|
42
|
+
expect(TestSubClassAttributes2.attr2).to eq(1)
|
43
|
+
end
|
33
44
|
end
|
@@ -21,4 +21,20 @@ describe Volt::ArrayModel do
|
|
21
21
|
Volt::Computation.flush!
|
22
22
|
expect(count).to eq(2)
|
23
23
|
end
|
24
|
+
|
25
|
+
it 'should return the index of a model' do
|
26
|
+
array_model = Volt::ArrayModel.new([1,2,3])
|
27
|
+
|
28
|
+
expect(array_model.index(2)).to eq(1)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should flatten' do
|
32
|
+
array = Volt::ArrayModel.new([])
|
33
|
+
|
34
|
+
array << Volt::ArrayModel.new([Volt::ArrayModel.new([1,2]), Volt::ArrayModel.new([3])])
|
35
|
+
array << Volt::ArrayModel.new([Volt::ArrayModel.new([4,5]), Volt::ArrayModel.new([6])])
|
36
|
+
|
37
|
+
expect(array.flatten.size).to eq(6)
|
38
|
+
expect(array.to_a.flatten.size).to eq(6)
|
39
|
+
end
|
24
40
|
end
|
@@ -6,6 +6,9 @@ class ExampleModelWithField < Volt::Model
|
|
6
6
|
field :value, Numeric
|
7
7
|
end
|
8
8
|
|
9
|
+
class ExampleModelWithField2 < ExampleModelWithField
|
10
|
+
end
|
11
|
+
|
9
12
|
describe 'field helpers' do
|
10
13
|
let(:model) { ExampleModelWithField.new }
|
11
14
|
it 'should allow a user to setup a field that can be written to and read' do
|
@@ -23,7 +26,7 @@ describe 'field helpers' do
|
|
23
26
|
|
24
27
|
it 'should raise an error when an invalid cast type is provided' do
|
25
28
|
expect do
|
26
|
-
|
29
|
+
ExampleModelWithField2.field :awesome, Array
|
27
30
|
end.to raise_error(FieldHelpers::InvalidFieldClass)
|
28
31
|
end
|
29
32
|
|
@@ -42,4 +45,8 @@ describe 'field helpers' do
|
|
42
45
|
expect(error).to eq({})
|
43
46
|
end
|
44
47
|
end
|
48
|
+
|
49
|
+
it 'should track the fields on the model class' do
|
50
|
+
expect(ExampleModelWithField.fields_data).to eq({:name=>[nil, {}], :value=>[Numeric, {}]})
|
51
|
+
end
|
45
52
|
end
|
@@ -46,6 +46,17 @@ class ::TestUpdateReadCheck < Volt::Model
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
+
class ::TestPromisePermission < Volt::Model
|
50
|
+
attr_reader :called_deny
|
51
|
+
permissions(:create) do
|
52
|
+
$test_promise = Promise.new
|
53
|
+
$test_promise.then do
|
54
|
+
@called_deny = true
|
55
|
+
deny
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
49
60
|
describe 'model permissions' do
|
50
61
|
let(:user_todo) { TestUserTodo.new }
|
51
62
|
|
@@ -162,5 +173,19 @@ describe 'model permissions' do
|
|
162
173
|
|
163
174
|
expect(model.read_check).to eq(nil)
|
164
175
|
end
|
176
|
+
|
177
|
+
it 'should allow permission blocks to return a promise' do
|
178
|
+
promise = store._test_promise_permissions.create({})
|
179
|
+
|
180
|
+
expect(promise.resolved?).to eq(false)
|
181
|
+
expect(promise.rejected?).to eq(false)
|
182
|
+
$test_promise.resolve(nil)
|
183
|
+
|
184
|
+
expect(promise.resolved?).to eq(false)
|
185
|
+
expect(promise.rejected?).to eq(true)
|
186
|
+
# puts "#{promise.error.inspect}"
|
187
|
+
# puts promise.error.backtrace.join("\n")
|
188
|
+
expect(promise.error.to_s).to match(/permissions did not allow create for/)
|
189
|
+
end
|
165
190
|
end
|
166
191
|
end
|
data/volt.gemspec
CHANGED
@@ -44,7 +44,7 @@ Gem::Specification.new do |spec|
|
|
44
44
|
spec.add_development_dependency 'capybara', '~> 2.4.2'
|
45
45
|
|
46
46
|
# There is a big performance issue with selenium-webdriver on v2.45.0
|
47
|
-
spec.add_development_dependency 'selenium-webdriver', '~> 2.
|
47
|
+
spec.add_development_dependency 'selenium-webdriver', '~> 2.46.2'
|
48
48
|
spec.add_development_dependency 'chromedriver2-helper', '~> 0.0.8'
|
49
49
|
spec.add_development_dependency 'poltergeist', '~> 1.5.0'
|
50
50
|
spec.add_development_dependency 'thin', '~> 1.6.3'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: volt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.5.
|
4
|
+
version: 0.9.5.pre4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Stout
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-07-
|
11
|
+
date: 2015-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -212,14 +212,14 @@ dependencies:
|
|
212
212
|
requirements:
|
213
213
|
- - "~>"
|
214
214
|
- !ruby/object:Gem::Version
|
215
|
-
version: 2.
|
215
|
+
version: 2.46.2
|
216
216
|
type: :development
|
217
217
|
prerelease: false
|
218
218
|
version_requirements: !ruby/object:Gem::Requirement
|
219
219
|
requirements:
|
220
220
|
- - "~>"
|
221
221
|
- !ruby/object:Gem::Version
|
222
|
-
version: 2.
|
222
|
+
version: 2.46.2
|
223
223
|
- !ruby/object:Gem::Dependency
|
224
224
|
name: chromedriver2-helper
|
225
225
|
requirement: !ruby/object:Gem::Requirement
|
@@ -374,6 +374,7 @@ files:
|
|
374
374
|
- app/volt/assets/js/volt_watch.js
|
375
375
|
- app/volt/config/dependencies.rb
|
376
376
|
- app/volt/controllers/notices_controller.rb
|
377
|
+
- app/volt/models/active_volt_instance.rb
|
377
378
|
- app/volt/models/user.rb
|
378
379
|
- app/volt/tasks/live_query/live_query.rb
|
379
380
|
- app/volt/tasks/live_query/live_query_pool.rb
|
@@ -572,7 +573,6 @@ files:
|
|
572
573
|
- lib/volt/utils/logging/task_logger.rb
|
573
574
|
- lib/volt/utils/modes.rb
|
574
575
|
- lib/volt/utils/parsing.rb
|
575
|
-
- lib/volt/utils/promise.rb
|
576
576
|
- lib/volt/utils/promise_extensions.rb
|
577
577
|
- lib/volt/utils/read_write_lock.rb
|
578
578
|
- lib/volt/utils/set_patch.rb
|
data/lib/volt/utils/promise.rb
DELETED
@@ -1,429 +0,0 @@
|
|
1
|
-
# A copy of the opal 0.8 promise library. The one in 0.7.x has some bugs.
|
2
|
-
|
3
|
-
# {Promise} is used to help structure asynchronous code.
|
4
|
-
#
|
5
|
-
# It is available in the Opal standard library, and can be required in any Opal
|
6
|
-
# application:
|
7
|
-
#
|
8
|
-
# require 'promise'
|
9
|
-
#
|
10
|
-
# ## Basic Usage
|
11
|
-
#
|
12
|
-
# Promises are created and returned as objects with the assumption that they
|
13
|
-
# will eventually be resolved or rejected, but never both. A {Promise} has
|
14
|
-
# a {#then} and {#fail} method (or one of their aliases) that can be used to
|
15
|
-
# register a block that gets called once resolved or rejected.
|
16
|
-
#
|
17
|
-
# promise = Promise.new
|
18
|
-
#
|
19
|
-
# promise.then {
|
20
|
-
# puts "resolved!"
|
21
|
-
# }.fail {
|
22
|
-
# puts "rejected!"
|
23
|
-
# }
|
24
|
-
#
|
25
|
-
# # some time later
|
26
|
-
# promise.resolve
|
27
|
-
#
|
28
|
-
# # => "resolved!"
|
29
|
-
#
|
30
|
-
# It is important to remember that a promise can only be resolved or rejected
|
31
|
-
# once, so the block will only ever be called once (or not at all).
|
32
|
-
#
|
33
|
-
# ## Resolving Promises
|
34
|
-
#
|
35
|
-
# To resolve a promise, means to inform the {Promise} that it has succeeded
|
36
|
-
# or evaluated to a useful value. {#resolve} can be passed a value which is
|
37
|
-
# then passed into the block handler:
|
38
|
-
#
|
39
|
-
# def get_json
|
40
|
-
# promise = Promise.new
|
41
|
-
#
|
42
|
-
# HTTP.get("some_url") do |req|
|
43
|
-
# promise.resolve req.json
|
44
|
-
# end
|
45
|
-
#
|
46
|
-
# promise
|
47
|
-
# end
|
48
|
-
#
|
49
|
-
# get_json.then do |json|
|
50
|
-
# puts "got some JSON from server"
|
51
|
-
# end
|
52
|
-
#
|
53
|
-
# ## Rejecting Promises
|
54
|
-
#
|
55
|
-
# Promises are also designed to handle error cases, or situations where an
|
56
|
-
# outcome is not as expected. Taking the previous example, we can also pass
|
57
|
-
# a value to a {#reject} call, which passes that object to the registered
|
58
|
-
# {#fail} handler:
|
59
|
-
#
|
60
|
-
# def get_json
|
61
|
-
# promise = Promise.new
|
62
|
-
#
|
63
|
-
# HTTP.get("some_url") do |req|
|
64
|
-
# if req.ok?
|
65
|
-
# promise.resolve req.json
|
66
|
-
# else
|
67
|
-
# promise.reject req
|
68
|
-
# end
|
69
|
-
#
|
70
|
-
# promise
|
71
|
-
# end
|
72
|
-
#
|
73
|
-
# get_json.then {
|
74
|
-
# # ...
|
75
|
-
# }.fail { |req|
|
76
|
-
# puts "it went wrong: #{req.message}"
|
77
|
-
# }
|
78
|
-
#
|
79
|
-
# ## Chaining Promises
|
80
|
-
#
|
81
|
-
# Promises become even more useful when chained together. Each {#then} or
|
82
|
-
# {#fail} call returns a new {Promise} which can be used to chain more and more
|
83
|
-
# handlers together.
|
84
|
-
#
|
85
|
-
# promise.then { wait_for_something }.then { do_something_else }
|
86
|
-
#
|
87
|
-
# Rejections are propagated through the entire chain, so a "catch all" handler
|
88
|
-
# can be attached at the end of the tail:
|
89
|
-
#
|
90
|
-
# promise.then { ... }.then { ... }.fail { ... }
|
91
|
-
#
|
92
|
-
# ## Composing Promises
|
93
|
-
#
|
94
|
-
# {Promise.when} can be used to wait for more than one promise to resolve (or
|
95
|
-
# reject). Using the previous example, we could request two different json
|
96
|
-
# requests and wait for both to finish:
|
97
|
-
#
|
98
|
-
# Promise.when(get_json, get_json2).then |first, second|
|
99
|
-
# puts "got two json payloads: #{first}, #{second}"
|
100
|
-
# end
|
101
|
-
#
|
102
|
-
class Promise
|
103
|
-
def self.value(value)
|
104
|
-
new.resolve(value)
|
105
|
-
end
|
106
|
-
|
107
|
-
def self.error(value)
|
108
|
-
new.reject(value)
|
109
|
-
end
|
110
|
-
|
111
|
-
def self.when(*promises)
|
112
|
-
When.new(promises)
|
113
|
-
end
|
114
|
-
|
115
|
-
attr_reader :error, :prev, :next
|
116
|
-
|
117
|
-
def initialize(action = {})
|
118
|
-
@action = action
|
119
|
-
|
120
|
-
@realized = false
|
121
|
-
@exception = false
|
122
|
-
@value = nil
|
123
|
-
@error = nil
|
124
|
-
@delayed = false
|
125
|
-
|
126
|
-
@prev = nil
|
127
|
-
@next = nil
|
128
|
-
end
|
129
|
-
|
130
|
-
def value
|
131
|
-
if Promise === @value
|
132
|
-
@value.value
|
133
|
-
else
|
134
|
-
@value
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
def act?
|
139
|
-
@action.has_key?(:success) || @action.has_key?(:always)
|
140
|
-
end
|
141
|
-
|
142
|
-
def action
|
143
|
-
@action.keys
|
144
|
-
end
|
145
|
-
|
146
|
-
def exception?
|
147
|
-
@exception
|
148
|
-
end
|
149
|
-
|
150
|
-
def realized?
|
151
|
-
!!@realized
|
152
|
-
end
|
153
|
-
|
154
|
-
def resolved?
|
155
|
-
@realized == :resolve
|
156
|
-
end
|
157
|
-
|
158
|
-
def rejected?
|
159
|
-
@realized == :reject
|
160
|
-
end
|
161
|
-
|
162
|
-
def ^(promise)
|
163
|
-
promise << self
|
164
|
-
self >> promise
|
165
|
-
|
166
|
-
promise
|
167
|
-
end
|
168
|
-
|
169
|
-
def <<(promise)
|
170
|
-
@prev = promise
|
171
|
-
|
172
|
-
self
|
173
|
-
end
|
174
|
-
|
175
|
-
def >>(promise)
|
176
|
-
@next = promise
|
177
|
-
|
178
|
-
if exception?
|
179
|
-
promise.reject(@delayed[0])
|
180
|
-
elsif resolved?
|
181
|
-
promise.resolve(@delayed ? @delayed[0] : value)
|
182
|
-
elsif rejected?
|
183
|
-
if !@action.has_key?(:failure) || Promise === (@delayed ? @delayed[0] : @error)
|
184
|
-
promise.reject(@delayed ? @delayed[0] : error)
|
185
|
-
elsif promise.action.include?(:always)
|
186
|
-
promise.reject(@delayed ? @delayed[0] : error)
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
self
|
191
|
-
end
|
192
|
-
|
193
|
-
def resolve(value = nil)
|
194
|
-
if realized?
|
195
|
-
raise ArgumentError, 'the promise has already been realized'
|
196
|
-
end
|
197
|
-
|
198
|
-
if Promise === value
|
199
|
-
return (value << @prev) ^ self
|
200
|
-
end
|
201
|
-
|
202
|
-
begin
|
203
|
-
if block = @action[:success] || @action[:always]
|
204
|
-
value = block.call(value)
|
205
|
-
end
|
206
|
-
|
207
|
-
resolve!(value)
|
208
|
-
rescue Exception => e
|
209
|
-
exception!(e)
|
210
|
-
end
|
211
|
-
|
212
|
-
self
|
213
|
-
end
|
214
|
-
|
215
|
-
def resolve!(value)
|
216
|
-
@realized = :resolve
|
217
|
-
@value = value
|
218
|
-
|
219
|
-
if @next
|
220
|
-
@next.resolve(value)
|
221
|
-
else
|
222
|
-
@delayed = [value]
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
def reject(value = nil)
|
227
|
-
if realized?
|
228
|
-
raise ArgumentError, 'the promise has already been realized'
|
229
|
-
end
|
230
|
-
|
231
|
-
if Promise === value
|
232
|
-
return (value << @prev) ^ self
|
233
|
-
end
|
234
|
-
|
235
|
-
begin
|
236
|
-
if block = @action[:failure] || @action[:always]
|
237
|
-
value = block.call(value)
|
238
|
-
end
|
239
|
-
|
240
|
-
if @action.has_key?(:always)
|
241
|
-
resolve!(value)
|
242
|
-
else
|
243
|
-
reject!(value)
|
244
|
-
end
|
245
|
-
rescue Exception => e
|
246
|
-
exception!(e)
|
247
|
-
end
|
248
|
-
|
249
|
-
self
|
250
|
-
end
|
251
|
-
|
252
|
-
def reject!(value)
|
253
|
-
@realized = :reject
|
254
|
-
@error = value
|
255
|
-
|
256
|
-
if @next
|
257
|
-
@next.reject(value)
|
258
|
-
else
|
259
|
-
@delayed = [value]
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
def exception!(error)
|
264
|
-
@exception = true
|
265
|
-
|
266
|
-
reject!(error)
|
267
|
-
end
|
268
|
-
|
269
|
-
def then(&block)
|
270
|
-
if @next
|
271
|
-
raise ArgumentError, 'a promise has already been chained'
|
272
|
-
end
|
273
|
-
|
274
|
-
self ^ Promise.new(success: block)
|
275
|
-
end
|
276
|
-
|
277
|
-
alias do then
|
278
|
-
|
279
|
-
def fail(&block)
|
280
|
-
if @next
|
281
|
-
raise ArgumentError, 'a promise has already been chained'
|
282
|
-
end
|
283
|
-
|
284
|
-
self ^ Promise.new(failure: block)
|
285
|
-
end
|
286
|
-
|
287
|
-
alias rescue fail
|
288
|
-
alias catch fail
|
289
|
-
|
290
|
-
def always(&block)
|
291
|
-
if @next
|
292
|
-
raise ArgumentError, 'a promise has already been chained'
|
293
|
-
end
|
294
|
-
|
295
|
-
self ^ Promise.new(always: block)
|
296
|
-
end
|
297
|
-
|
298
|
-
alias finally always
|
299
|
-
alias ensure always
|
300
|
-
|
301
|
-
def trace(depth = nil, &block)
|
302
|
-
if @next
|
303
|
-
raise ArgumentError, 'a promise has already been chained'
|
304
|
-
end
|
305
|
-
|
306
|
-
self ^ Trace.new(depth, block)
|
307
|
-
end
|
308
|
-
|
309
|
-
def inspect
|
310
|
-
result = "#<#{self.class}(#{object_id})"
|
311
|
-
|
312
|
-
if @next
|
313
|
-
result += " >> #{@next.inspect}"
|
314
|
-
end
|
315
|
-
|
316
|
-
if realized?
|
317
|
-
result += ": #{(@value || @error).inspect}>"
|
318
|
-
else
|
319
|
-
result += ">"
|
320
|
-
end
|
321
|
-
|
322
|
-
result
|
323
|
-
end
|
324
|
-
|
325
|
-
class Trace < self
|
326
|
-
def self.it(promise)
|
327
|
-
current = []
|
328
|
-
|
329
|
-
if promise.act? || promise.prev.nil?
|
330
|
-
current.push(promise.value)
|
331
|
-
end
|
332
|
-
|
333
|
-
if prev = promise.prev
|
334
|
-
current.concat(it(prev))
|
335
|
-
else
|
336
|
-
current
|
337
|
-
end
|
338
|
-
end
|
339
|
-
|
340
|
-
def initialize(depth, block)
|
341
|
-
@depth = depth
|
342
|
-
|
343
|
-
super success: -> {
|
344
|
-
trace = Trace.it(self).reverse
|
345
|
-
trace.pop
|
346
|
-
|
347
|
-
if depth && depth <= trace.length
|
348
|
-
trace.shift(trace.length - depth)
|
349
|
-
end
|
350
|
-
|
351
|
-
block.call(*trace)
|
352
|
-
}
|
353
|
-
end
|
354
|
-
end
|
355
|
-
|
356
|
-
class When < self
|
357
|
-
def initialize(promises = [])
|
358
|
-
super()
|
359
|
-
|
360
|
-
@wait = []
|
361
|
-
|
362
|
-
promises.each {|promise|
|
363
|
-
wait promise
|
364
|
-
}
|
365
|
-
end
|
366
|
-
|
367
|
-
def each(&block)
|
368
|
-
raise ArgumentError, 'no block given' unless block
|
369
|
-
|
370
|
-
self.then {|values|
|
371
|
-
values.each(&block)
|
372
|
-
}
|
373
|
-
end
|
374
|
-
|
375
|
-
def collect(&block)
|
376
|
-
raise ArgumentError, 'no block given' unless block
|
377
|
-
|
378
|
-
self.then {|values|
|
379
|
-
When.new(values.map(&block))
|
380
|
-
}
|
381
|
-
end
|
382
|
-
|
383
|
-
def inject(*args, &block)
|
384
|
-
self.then {|values|
|
385
|
-
values.reduce(*args, &block)
|
386
|
-
}
|
387
|
-
end
|
388
|
-
|
389
|
-
alias map collect
|
390
|
-
|
391
|
-
alias reduce inject
|
392
|
-
|
393
|
-
def wait(promise)
|
394
|
-
unless Promise === promise
|
395
|
-
promise = Promise.value(promise)
|
396
|
-
end
|
397
|
-
|
398
|
-
if promise.act?
|
399
|
-
promise = promise.then
|
400
|
-
end
|
401
|
-
|
402
|
-
@wait << promise
|
403
|
-
|
404
|
-
promise.always {
|
405
|
-
try if @next
|
406
|
-
}
|
407
|
-
|
408
|
-
self
|
409
|
-
end
|
410
|
-
|
411
|
-
alias and wait
|
412
|
-
|
413
|
-
def >>(*)
|
414
|
-
super.tap {
|
415
|
-
try
|
416
|
-
}
|
417
|
-
end
|
418
|
-
|
419
|
-
def try
|
420
|
-
if @wait.all?(&:realized?)
|
421
|
-
if promise = @wait.find(&:rejected?)
|
422
|
-
reject(promise.error)
|
423
|
-
else
|
424
|
-
resolve(@wait.map(&:value))
|
425
|
-
end
|
426
|
-
end
|
427
|
-
end
|
428
|
-
end
|
429
|
-
end
|