volt 0.9.3.pre5 → 0.9.3.pre6
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/tasks/store_tasks.rb +0 -1
- data/lib/volt/cli/new_gem.rb +3 -9
- data/lib/volt/data_stores/data_store.rb +1 -1
- data/lib/volt/models/persistors/local_store.rb +3 -3
- data/lib/volt/models/persistors/model_store.rb +0 -5
- data/lib/volt/models/url.rb +10 -6
- data/lib/volt/page/channel.rb +3 -3
- data/lib/volt/page/page.rb +2 -1
- data/lib/volt/page/tasks.rb +3 -1
- data/lib/volt/reactive/reactive_array.rb +2 -1
- data/lib/volt/server.rb +4 -0
- data/lib/volt/server/forking_server.rb +41 -4
- data/lib/volt/server/forking_server/boot_error.html.erb +42 -0
- data/lib/volt/server/rack/opal_files.rb +1 -1
- data/lib/volt/server/socket_connection_handler.rb +19 -10
- data/lib/volt/server/websocket/websocket_handler.rb +1 -4
- data/lib/volt/spec/setup.rb +9 -1
- data/lib/volt/tasks/dispatcher.rb +10 -6
- data/lib/volt/utils/ejson.rb +55 -6
- data/lib/volt/utils/promise_extensions.rb +31 -8
- data/lib/volt/version.rb +1 -1
- data/lib/volt/volt/server_setup/app.rb +2 -1
- data/spec/models/model_spec.rb +10 -8
- data/spec/models/validators/length_validator_spec.rb +2 -2
- data/spec/models/validators/phone_number_validator_spec.rb +2 -2
- data/spec/utils/ejson_spec.rb +101 -0
- data/spec/utils/promise_extensions_spec.rb +16 -0
- data/templates/newgem/newgem.gemspec.tt +1 -0
- data/templates/newgem/spec/spec_helper.rb.tt +14 -2
- data/templates/project/config/base/index.html +4 -5
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6b47ed7510baf946cc2e65e7140be76022472b53
|
4
|
+
data.tar.gz: cf681b804b8609987671fbf2626aaf399d39bec0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ffcd6ef512a9b74b519b1b2ee294b8bef5d00075b8052ab6c4cee1375fac9662d2d2991024a469ccb7b8cf3c0e1d7388999dc6e5ff908f1b48a6705e8cef12df
|
7
|
+
data.tar.gz: 811b21c5c24ee684593c7069aba0cd68fad6480b22486c0646f34e67f365b8ab7e0ef36857c0defba4030fc9be243745f27c9a2e8930a7ba6fe58a29afc3dad0
|
data/CHANGELOG.md
CHANGED
@@ -19,6 +19,8 @@
|
|
19
19
|
- Volt.current_user now works in HttpController's
|
20
20
|
- You can now add your own middleware to the middleware stack. (see docs)
|
21
21
|
- Added a threadpool for Tasks, and options to customize pool size in config/app.rb
|
22
|
+
- Volt now handles Syntax errors much better, it will display an error message when your app does not compile, and can reload from that page when things change. (in development)
|
23
|
+
- Time objects can now be saved in models.
|
22
24
|
|
23
25
|
### Changed
|
24
26
|
- All methods on ArrayModel's under the store collection now return a Promise.
|
data/lib/volt/cli/new_gem.rb
CHANGED
@@ -57,15 +57,9 @@ class NewGem
|
|
57
57
|
|
58
58
|
def copy_options
|
59
59
|
copy('newgem/bin/newgem.tt', "bin/#{@name}") if @options[:bin]
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
copy('newgem/spec/spec_helper.rb.tt', 'spec/spec_helper.rb')
|
64
|
-
copy('newgem/spec/newgem_spec.rb.tt', "spec/#{@namespaced_path}_spec.rb")
|
65
|
-
when 'minitest'
|
66
|
-
copy('newgem/test/minitest_helper.rb.tt', 'test/minitest_helper.rb')
|
67
|
-
copy('newgem/test/test_newgem.rb.tt', "test/test_#{@namespaced_path}.rb")
|
68
|
-
end
|
60
|
+
copy('newgem/rspec.tt', '.rspec')
|
61
|
+
copy('newgem/spec/spec_helper.rb.tt', 'spec/spec_helper.rb')
|
62
|
+
copy('newgem/spec/newgem_spec.rb.tt', "spec/#{@namespaced_path}_spec.rb")
|
69
63
|
puts "Initializing git repo in #{@target}"
|
70
64
|
Dir.chdir(@target) { `git init`; `git add .` }
|
71
65
|
|
@@ -14,7 +14,7 @@ module Volt
|
|
14
14
|
adaptor_name = root.const_get(adaptor_name)
|
15
15
|
@adaptor = adaptor_name.new
|
16
16
|
else
|
17
|
-
raise "#{database_name} is not a supported database"
|
17
|
+
raise "#{database_name} is not a supported database, you might be missing a volt-#{database_name} gem"
|
18
18
|
end
|
19
19
|
|
20
20
|
@adaptor
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'volt/models/persistors/base'
|
2
2
|
require 'volt/utils/local_storage'
|
3
|
-
require '
|
3
|
+
require 'volt/utils/ejson'
|
4
4
|
|
5
5
|
module Volt
|
6
6
|
module Persistors
|
@@ -17,7 +17,7 @@ module Volt
|
|
17
17
|
if @model.path == []
|
18
18
|
json_data = LocalStorage['volt-store']
|
19
19
|
if json_data
|
20
|
-
root_attributes =
|
20
|
+
root_attributes = EJSON.parse(json_data)
|
21
21
|
|
22
22
|
@loading_data = true
|
23
23
|
root_attributes.each_pair do |key, value|
|
@@ -37,7 +37,7 @@ module Volt
|
|
37
37
|
def save_all
|
38
38
|
return if @loading_data
|
39
39
|
|
40
|
-
json_data =
|
40
|
+
json_data = EJSON.stringify(@model.to_h)
|
41
41
|
|
42
42
|
LocalStorage['volt-store'] = json_data
|
43
43
|
end
|
data/lib/volt/models/url.rb
CHANGED
@@ -8,15 +8,18 @@ module Volt
|
|
8
8
|
include ReactiveAccessors
|
9
9
|
|
10
10
|
# TODO: we need to make it so change events only trigger on changes
|
11
|
-
reactive_accessor :scheme, :host, :port, :path, :query, :
|
11
|
+
reactive_accessor :scheme, :host, :port, :path, :query, :fragment
|
12
12
|
attr_accessor :router
|
13
13
|
|
14
14
|
def initialize(router = nil)
|
15
15
|
@router = router
|
16
|
-
@params = Model.new({}, persistor: Persistors::Params)
|
17
16
|
@location = Location.new
|
18
17
|
end
|
19
18
|
|
19
|
+
def params
|
20
|
+
@params ||= Model.new({}, persistor: Persistors::Params)
|
21
|
+
end
|
22
|
+
|
20
23
|
# Parse takes in a url and extracts each sections.
|
21
24
|
# It also assigns and changes to the params.
|
22
25
|
def parse(url)
|
@@ -95,7 +98,7 @@ module Volt
|
|
95
98
|
end
|
96
99
|
|
97
100
|
def url_with(params)
|
98
|
-
url_for(
|
101
|
+
url_for(params.to_h.merge(params))
|
99
102
|
end
|
100
103
|
|
101
104
|
# Called when the state has changed and the url in the
|
@@ -103,7 +106,7 @@ module Volt
|
|
103
106
|
# Called when an attribute changes to update the url
|
104
107
|
def update!
|
105
108
|
if Volt.in_browser?
|
106
|
-
new_url = url_for(
|
109
|
+
new_url = url_for(params.to_h)
|
107
110
|
|
108
111
|
# Push the new url if pushState is supported
|
109
112
|
# TODO: add fragment fallback
|
@@ -158,8 +161,9 @@ module Volt
|
|
158
161
|
query_hash.merge!(new_params)
|
159
162
|
|
160
163
|
# Loop through the .params we already have assigned.
|
161
|
-
|
162
|
-
|
164
|
+
lparams = params
|
165
|
+
assign_from_old(lparams, query_hash)
|
166
|
+
assign_new(lparams, query_hash)
|
163
167
|
end
|
164
168
|
|
165
169
|
# Loop through the old params, and overwrite any existing values,
|
data/lib/volt/page/channel.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# The channel is the connection between the front end and the backend.
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'volt/utils/ejson'
|
4
4
|
require 'volt/reactive/reactive_accessors'
|
5
5
|
require 'volt/reactive/eventable'
|
6
6
|
|
@@ -94,7 +94,7 @@ module Volt
|
|
94
94
|
end
|
95
95
|
|
96
96
|
def message_received(message)
|
97
|
-
message =
|
97
|
+
message = EJSON.parse(message)
|
98
98
|
|
99
99
|
trigger!('message', *message)
|
100
100
|
end
|
@@ -104,7 +104,7 @@ module Volt
|
|
104
104
|
@queue << message
|
105
105
|
else
|
106
106
|
# TODO: Temp: wrap message in an array, so we're sure its valid JSON
|
107
|
-
message =
|
107
|
+
message = EJSON.stringify([message])
|
108
108
|
`
|
109
109
|
this.socket.send(message);
|
110
110
|
`
|
data/lib/volt/page/page.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'volt/utils/ejson'
|
1
2
|
|
2
3
|
module Volt
|
3
4
|
class Page
|
@@ -178,7 +179,7 @@ module Volt
|
|
178
179
|
`if (page_obj_str) {`
|
179
180
|
`sessionStorage.removeItem('___page');`
|
180
181
|
|
181
|
-
|
182
|
+
EJSON.parse(page_obj_str).each_pair do |key, value|
|
182
183
|
page.send(:"_#{key}=", value)
|
183
184
|
end
|
184
185
|
`}`
|
data/lib/volt/page/tasks.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'volt/utils/ejson'
|
2
|
+
|
1
3
|
module Volt
|
2
4
|
# The tasks class provides an interface to call tasks on
|
3
5
|
# the backend server. This class is setup as page.task (as a singleton)
|
@@ -63,7 +65,7 @@ module Volt
|
|
63
65
|
|
64
66
|
def reload
|
65
67
|
# Stash the current page value
|
66
|
-
value =
|
68
|
+
value = EJSON.stringify($page.page.to_h)
|
67
69
|
|
68
70
|
# If this browser supports session storage, store the page, so it will
|
69
71
|
# be in the same state when we reload.
|
data/lib/volt/server.rb
CHANGED
@@ -17,6 +17,7 @@ require 'volt/page/page'
|
|
17
17
|
require 'volt/server/websocket/websocket_handler'
|
18
18
|
require 'volt/utils/read_write_lock'
|
19
19
|
require 'volt/server/forking_server'
|
20
|
+
require 'volt/server/websocket/rack_server_adaptor'
|
20
21
|
|
21
22
|
|
22
23
|
module Volt
|
@@ -49,6 +50,9 @@ module Volt
|
|
49
50
|
# killed when code changes and reforked. (This provides simple fast code
|
50
51
|
# reloading)
|
51
52
|
def app
|
53
|
+
# Setup the rack server and adaptor
|
54
|
+
RackServerAdaptor.load
|
55
|
+
|
52
56
|
app = Rack::Builder.new
|
53
57
|
|
54
58
|
# Handle websocket connections
|
@@ -4,6 +4,15 @@ require 'drb'
|
|
4
4
|
require 'stringio'
|
5
5
|
require 'listen'
|
6
6
|
|
7
|
+
class ErrorDispatcher
|
8
|
+
def dispatch(channel, message)
|
9
|
+
Volt.logger.error("The app failed to start, so the following message can not be run: #{message}")
|
10
|
+
end
|
11
|
+
|
12
|
+
def close_channel(channel)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
7
16
|
module Volt
|
8
17
|
class ForkingServer
|
9
18
|
def initialize(server)
|
@@ -58,11 +67,17 @@ module Volt
|
|
58
67
|
# Running as child
|
59
68
|
@reader.close
|
60
69
|
|
61
|
-
|
62
|
-
|
70
|
+
begin
|
71
|
+
volt_app = @server.boot_volt
|
72
|
+
@rack_app = volt_app.middleware
|
73
|
+
|
74
|
+
# Set the drb object locally
|
75
|
+
@dispatcher = Dispatcher.new(volt_app)
|
76
|
+
rescue Exception => error
|
77
|
+
boot_error(error)
|
78
|
+
end
|
79
|
+
|
63
80
|
|
64
|
-
# Set the drb object locally
|
65
|
-
@dispatcher = Dispatcher.new(volt_app)
|
66
81
|
drb_object = DRb.start_service('drbunix:', [self, @dispatcher])
|
67
82
|
|
68
83
|
@writer.puts(drb_object.uri)
|
@@ -79,6 +94,28 @@ module Volt
|
|
79
94
|
end
|
80
95
|
end
|
81
96
|
|
97
|
+
# called from the child when the boot failes. Sets up an error page rack
|
98
|
+
# app to show the user the error and handle reloading requests.
|
99
|
+
def boot_error(error)
|
100
|
+
msg = error.inspect
|
101
|
+
if error.respond_to?(:backtrace)
|
102
|
+
msg << "\n" + error.backtrace.join("\n")
|
103
|
+
end
|
104
|
+
Volt.logger.error(msg)
|
105
|
+
|
106
|
+
# Only require when needed
|
107
|
+
require 'cgi'
|
108
|
+
@rack_app = Proc.new do
|
109
|
+
path = File.join(File.dirname(__FILE__), "forking_server/boot_error.html.erb")
|
110
|
+
html = File.read(path)
|
111
|
+
error_page = ERB.new(html, nil, '-').result(binding)
|
112
|
+
|
113
|
+
[500, {"Content-Type" => "text/html"}, error_page]
|
114
|
+
end
|
115
|
+
|
116
|
+
@dispatcher = ErrorDispatcher.new
|
117
|
+
end
|
118
|
+
|
82
119
|
|
83
120
|
def stop_child
|
84
121
|
# clear the drb object and kill the child process.
|
@@ -0,0 +1,42 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8" />
|
5
|
+
|
6
|
+
<script>
|
7
|
+
// Simple code to handle reload messages
|
8
|
+
if (document.location.protocol == 'https:') {
|
9
|
+
var wsProto = 'wss';
|
10
|
+
} else {
|
11
|
+
var wsProto = 'ws';
|
12
|
+
}
|
13
|
+
|
14
|
+
this.socket = new WebSocket(wsProto + '://' + document.location.host + '/socket');
|
15
|
+
|
16
|
+
// Log errors
|
17
|
+
this.socket.onerror = function (error) {
|
18
|
+
document.location.reload();
|
19
|
+
};
|
20
|
+
|
21
|
+
// Log messages from the server
|
22
|
+
this.socket.onmessage = function(message) {
|
23
|
+
if (message.data == '["reload"]') {
|
24
|
+
document.location.reload();
|
25
|
+
}
|
26
|
+
};
|
27
|
+
|
28
|
+
this.socket.onclose = function(error) {
|
29
|
+
document.location.reload();
|
30
|
+
};
|
31
|
+
</script>
|
32
|
+
</head>
|
33
|
+
<body>
|
34
|
+
<h2><%= CGI::escapeHTML(error.inspect) %></h2>
|
35
|
+
|
36
|
+
<% if error.respond_to?(:backtrace) %>
|
37
|
+
<pre>
|
38
|
+
<%= error.backtrace.map {|l| CGI::escapeHTML(l) }.join("<br />") %>
|
39
|
+
</pre>
|
40
|
+
<% end %>
|
41
|
+
</body>
|
42
|
+
</html>
|
@@ -22,7 +22,7 @@ module Volt
|
|
22
22
|
Opal.append_path(Volt.root + '/app')
|
23
23
|
Opal.append_path(Volt.root + '/lib')
|
24
24
|
|
25
|
-
Gem.loaded_specs.values.select {|gem| gem.name =~ /^volt/ }
|
25
|
+
Gem.loaded_specs.values.select {|gem| gem.name =~ /^(volt|ejson_ext)/ }
|
26
26
|
.each do |gem|
|
27
27
|
['app', 'lib'].each do |folder|
|
28
28
|
path = gem.full_gem_path + "/#{folder}"
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'volt/utils/ejson'
|
2
2
|
require File.join(File.dirname(__FILE__), '../../../app/volt/tasks/query_tasks')
|
3
3
|
|
4
4
|
module Volt
|
@@ -9,6 +9,14 @@ module Volt
|
|
9
9
|
# This may be changed as new listeners connect, which is fine.
|
10
10
|
attr_accessor :user_id
|
11
11
|
|
12
|
+
|
13
|
+
def initialize(session, *args)
|
14
|
+
@session = session
|
15
|
+
|
16
|
+
@@channels ||= []
|
17
|
+
@@channels << self
|
18
|
+
end
|
19
|
+
|
12
20
|
def self.dispatcher=(val)
|
13
21
|
@@dispatcher = val
|
14
22
|
end
|
@@ -26,17 +34,10 @@ module Volt
|
|
26
34
|
end
|
27
35
|
end
|
28
36
|
|
29
|
-
def initialize(session, *args)
|
30
|
-
@session = session
|
31
|
-
|
32
|
-
@@channels ||= []
|
33
|
-
@@channels << self
|
34
|
-
end
|
35
|
-
|
36
37
|
def process_message(message)
|
37
38
|
# self.class.message_all(message)
|
38
39
|
# Messages are json and wrapped in an array
|
39
|
-
message =
|
40
|
+
message = EJSON.parse(message).first
|
40
41
|
|
41
42
|
begin
|
42
43
|
@@dispatcher.dispatch(self, message)
|
@@ -51,9 +52,17 @@ module Volt
|
|
51
52
|
end
|
52
53
|
|
53
54
|
def send_message(*args)
|
54
|
-
str =
|
55
|
+
str = EJSON.stringify([*args])
|
55
56
|
|
56
57
|
@session.send(str)
|
58
|
+
|
59
|
+
if RUNNING_SERVER == 'thin'
|
60
|
+
# This might seem strange, but it prevents a delay with outgoing
|
61
|
+
# messages.
|
62
|
+
# TODO: Figure out the cause of the issue and submit a fix upstream.
|
63
|
+
EM.next_tick {}
|
64
|
+
end
|
65
|
+
|
57
66
|
end
|
58
67
|
|
59
68
|
def closed
|
@@ -1,13 +1,10 @@
|
|
1
1
|
require 'faye/websocket'
|
2
2
|
require 'volt/server/socket_connection_handler'
|
3
|
-
|
3
|
+
|
4
4
|
|
5
5
|
module Volt
|
6
6
|
class WebsocketHandler
|
7
7
|
def initialize(app)
|
8
|
-
# Setup the rack server and adaptor
|
9
|
-
RackServerAdaptor.load
|
10
|
-
|
11
8
|
@app = app
|
12
9
|
end
|
13
10
|
|
data/lib/volt/spec/setup.rb
CHANGED
@@ -49,7 +49,7 @@ module Volt
|
|
49
49
|
|
50
50
|
# Setup the spec collection accessors
|
51
51
|
# RSpec.shared_context "volt collections", {} do
|
52
|
-
RSpec.
|
52
|
+
RSpec.shared_context 'volt collections', {} do
|
53
53
|
# Page conflicts with capybara's page method, so we call it the_page for now.
|
54
54
|
# TODO: we need a better solution for page
|
55
55
|
|
@@ -60,7 +60,15 @@ module Volt
|
|
60
60
|
$page.store
|
61
61
|
end
|
62
62
|
let(:volt_app) { volt_app }
|
63
|
+
let(:params) { volt_app.page.params }
|
63
64
|
|
65
|
+
after do
|
66
|
+
# Clear params if used
|
67
|
+
url = volt_app.page.url
|
68
|
+
if url.instance_variable_get('@params')
|
69
|
+
url.instance_variable_set('@params', nil)
|
70
|
+
end
|
71
|
+
end
|
64
72
|
|
65
73
|
if RUBY_PLATFORM != 'opal'
|
66
74
|
after do |example|
|
@@ -2,6 +2,7 @@
|
|
2
2
|
require 'volt/utils/logging/task_logger'
|
3
3
|
require 'drb'
|
4
4
|
require 'concurrent'
|
5
|
+
require 'timeout'
|
5
6
|
|
6
7
|
module Volt
|
7
8
|
# The task dispatcher is responsible for taking incoming messages
|
@@ -95,11 +96,13 @@ module Volt
|
|
95
96
|
# Init and send the method
|
96
97
|
promise = promise.then do
|
97
98
|
result = nil
|
98
|
-
|
99
|
+
Timeout.timeout(klass.__timeout || @worker_timeout) do
|
99
100
|
Thread.current['meta'] = meta_data
|
100
|
-
|
101
|
-
|
102
|
-
|
101
|
+
begin
|
102
|
+
result = klass.new(@volt_app, channel, self).send(method_name, *args)
|
103
|
+
ensure
|
104
|
+
Thread.current['meta'] = nil
|
105
|
+
end
|
103
106
|
end
|
104
107
|
|
105
108
|
result
|
@@ -112,8 +115,9 @@ module Volt
|
|
112
115
|
|
113
116
|
# Called after task runs or fails
|
114
117
|
finish = proc do |error|
|
115
|
-
if error.is_a?(
|
116
|
-
|
118
|
+
if error.is_a?(Timeout::Error)
|
119
|
+
# re-raise with a message
|
120
|
+
error = Timeout::Error.new("Task Timed Out after #{@worker_timeout} seconds: #{message}")
|
117
121
|
end
|
118
122
|
|
119
123
|
run_time = ((Time.now.to_f - start_time) * 1000).round(3)
|
data/lib/volt/utils/ejson.rb
CHANGED
@@ -1,11 +1,60 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
1
3
|
module Volt
|
2
|
-
class
|
3
|
-
def self.
|
4
|
-
obj
|
4
|
+
class EJSON
|
5
|
+
def self.stringify(obj)
|
6
|
+
encode(obj).to_json
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.parse(str)
|
10
|
+
decode(JSON.parse(str))
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def self.decode(obj)
|
16
|
+
if Array === obj
|
17
|
+
obj.map {|v| decode(v) }
|
18
|
+
elsif Hash === obj
|
19
|
+
if obj.size == 1 && (escape = obj['$escape'])
|
20
|
+
return escape.map do |key, value|
|
21
|
+
[key, decode(value)]
|
22
|
+
end.to_h
|
23
|
+
elsif obj.size == 1 && (time = obj['$date'])
|
24
|
+
if time.is_a?(Fixnum)
|
25
|
+
return Time.at(time / 1000.0)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
obj.map do |key, value|
|
30
|
+
[key, decode(value)]
|
31
|
+
end.to_h
|
32
|
+
else
|
33
|
+
obj
|
34
|
+
end
|
5
35
|
end
|
6
36
|
|
7
|
-
def self.
|
8
|
-
|
37
|
+
def self.encode(obj)
|
38
|
+
if Array === obj
|
39
|
+
obj.map {|v| encode(v) }
|
40
|
+
elsif Hash === obj
|
41
|
+
obj.map do |key, value|
|
42
|
+
if key == '$date'
|
43
|
+
key = '$escape'
|
44
|
+
value = {'$date' => encode(value)}
|
45
|
+
else
|
46
|
+
value = encode(value)
|
47
|
+
end
|
48
|
+
|
49
|
+
[key, value]
|
50
|
+
end.to_h
|
51
|
+
else
|
52
|
+
if obj.is_a?(Time)
|
53
|
+
{'$date' => obj.to_i * 1_000}
|
54
|
+
else
|
55
|
+
obj
|
56
|
+
end
|
57
|
+
end
|
9
58
|
end
|
10
59
|
end
|
11
|
-
end
|
60
|
+
end
|
@@ -4,16 +4,25 @@ require 'volt/utils/promise'
|
|
4
4
|
# A temp patch for promises until https://github.com/opal/opal/pull/725 is released.
|
5
5
|
class Promise
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
7
|
+
# We made a choice not to support comparitors and << and >> on method_missing
|
8
|
+
# on Promises. This makes it easier to understand what promise proxying does
|
9
|
+
# and how it works. It also prevents confusing situations where you try to
|
10
|
+
# == compare two Promises for example. The cost though is more code to do
|
11
|
+
# comparisons, but we feel it is worth it.
|
12
|
+
def respond_to_missing?(method_name, include_private = false)
|
13
|
+
!!(method_name =~ /[a-z_]\w*[?!=]?/)
|
13
14
|
end
|
14
15
|
|
15
|
-
def
|
16
|
-
|
16
|
+
def method_missing(method_name, *args, &block)
|
17
|
+
if respond_to_missing?(method_name)
|
18
|
+
promise = self.then do |value|
|
19
|
+
value.send(method_name, *args, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
promise
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
17
26
|
end
|
18
27
|
|
19
28
|
# Allow .each to be called directly on promises
|
@@ -56,6 +65,20 @@ class Promise
|
|
56
65
|
@value || @error
|
57
66
|
end
|
58
67
|
|
68
|
+
# When testing with rspec, add in a custom exception! method that doesn't
|
69
|
+
# swallow ExpectationNotMetError's.
|
70
|
+
if defined?(RSpec::Expectations::ExpectationNotMetError)
|
71
|
+
def exception!(error)
|
72
|
+
if error.is_a?(RSpec::Expectations::ExpectationNotMetError)
|
73
|
+
raise error
|
74
|
+
end
|
75
|
+
@exception = true
|
76
|
+
|
77
|
+
reject!(error)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
59
82
|
|
60
83
|
# Waits for the promise to resolve (assuming it is blocking on
|
61
84
|
# the server) and returns the result.
|
data/lib/volt/version.rb
CHANGED
@@ -48,7 +48,8 @@ module Volt
|
|
48
48
|
|
49
49
|
# This config needs to run earlier than others
|
50
50
|
def run_config
|
51
|
-
|
51
|
+
path = "#{Volt.root}/config/app.rb"
|
52
|
+
require(path) if File.exists?(path)
|
52
53
|
end
|
53
54
|
|
54
55
|
# Load in all .rb files in the initializers folders and the config/app.rb
|
data/spec/models/model_spec.rb
CHANGED
@@ -232,18 +232,20 @@ describe Volt::Model do
|
|
232
232
|
expect(count).to eq(1)
|
233
233
|
end
|
234
234
|
|
235
|
-
|
236
|
-
|
235
|
+
unless RUBY_PLATFORM == 'opal'
|
236
|
+
it 'should track changes through an expansion' do
|
237
|
+
a = Volt::Model.new
|
237
238
|
|
238
|
-
|
239
|
-
|
239
|
+
last_count = 0
|
240
|
+
-> { last_count = a._todos.count(&:_checked).sync }.watch!
|
240
241
|
|
241
|
-
|
242
|
+
expect(last_count).to eq(0)
|
242
243
|
|
243
|
-
|
244
|
-
|
244
|
+
a._todos! << { checked: true }
|
245
|
+
Volt::Computation.flush!
|
245
246
|
|
246
|
-
|
247
|
+
expect(last_count).to eq(1)
|
248
|
+
end
|
247
249
|
end
|
248
250
|
|
249
251
|
it 'should call changed when a the reference to a submodel is assigned to another value' do
|
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Volt::LengthValidator do
|
4
|
-
subject { Volt::LengthValidator.validate(*
|
5
|
-
let(:
|
4
|
+
subject { Volt::LengthValidator.validate(*use_params) }
|
5
|
+
let(:use_params) { [model, field_name, options] }
|
6
6
|
|
7
7
|
let(:model) { Volt::Model.new name: name }
|
8
8
|
let(:field_name) { :name }
|
@@ -15,8 +15,8 @@ describe Volt::PhoneNumberValidator do
|
|
15
15
|
let(:valid_intl_number) { '+12 123 123 1234' }
|
16
16
|
let(:invalid_number) { '1234-123-123456' }
|
17
17
|
|
18
|
-
let(:validate) { described_class.validate(*
|
19
|
-
let(:
|
18
|
+
let(:validate) { described_class.validate(*use_params) }
|
19
|
+
let(:use_params) { [model, field_name, options] }
|
20
20
|
let(:message) { 'must be a phone number with area or country code' }
|
21
21
|
|
22
22
|
it_behaves_like 'a format validator'
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Volt::EJSON, '.parse' do
|
4
|
+
subject { Volt::EJSON }
|
5
|
+
let(:epoch) { 135820576553 }
|
6
|
+
let(:ruby_epoch) { epoch / 1000.0 }
|
7
|
+
|
8
|
+
context 'safe escaping' do
|
9
|
+
it 'does not parse date objects with invalid values' do
|
10
|
+
parsed = subject.parse '{"a" : {"$escape" : {"$date" : "something"}}}'
|
11
|
+
|
12
|
+
expect(parsed).to eq('a' => { '$date' => 'something' })
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'only escapes one level down' do
|
16
|
+
parsed = subject.parse %({"$escape": {"$date": {"$date": #{epoch}}}})
|
17
|
+
|
18
|
+
expect(parsed).to eq('$date' => Time.at(ruby_epoch))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'parsing EJSON fields' do
|
23
|
+
context 'date' do
|
24
|
+
it 'is not parsed when given a bad value' do
|
25
|
+
expect(subject.parse '{"a": {"$date" : "something"}}').
|
26
|
+
to eq('a' => { '$date' => 'something' })
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'parses proper $date EJSON fields' do
|
30
|
+
parsed = subject.parse '{"a" : {"$date": 135820576553}}'
|
31
|
+
|
32
|
+
expect(parsed['a']).to eq Time.at(ruby_epoch)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'parses nested EJSON date fields' do
|
36
|
+
parsed = subject.parse '{"a" : {"b" : {"$date": 135820576553}}}'
|
37
|
+
|
38
|
+
expect(parsed['a']['b']).to eq Time.at(ruby_epoch)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'parses nested $dates within $escapes' do
|
42
|
+
parsed = subject.parse(
|
43
|
+
'{"a" : {"$escape": {"$date" : {"date" : {"$date": 135820576553}}}}}'
|
44
|
+
)
|
45
|
+
|
46
|
+
expect(parsed['a']['$date']['date']).to eq Time.at(ruby_epoch)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'parses multiple EJSON date fields' do
|
50
|
+
ejson = begin
|
51
|
+
%({"when":{"$date":#{epoch}},"then":{"$date":#{epoch}}})
|
52
|
+
end
|
53
|
+
|
54
|
+
expect(subject.parse ejson).to eq(
|
55
|
+
"when" => Time.at(ruby_epoch),
|
56
|
+
"then" => Time.at(ruby_epoch)
|
57
|
+
)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe Volt::EJSON, '.stringify' do
|
64
|
+
subject { Volt::EJSON }
|
65
|
+
context 'marshaling dates' do
|
66
|
+
let(:now) { Time.now }
|
67
|
+
let(:now_js_epoch) { now.to_i * 1_000 }
|
68
|
+
|
69
|
+
it 'does nothing with regular hashes' do
|
70
|
+
stringified = subject.stringify plain: 'jane'
|
71
|
+
|
72
|
+
expect(stringified).to eq '{"plain":"jane"}'
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'marshals when given a date' do
|
76
|
+
stringified = subject.stringify when: now
|
77
|
+
|
78
|
+
expect(stringified).to eq %({"when":{"$date":#{now_js_epoch}}})
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'marshals nested dates' do
|
82
|
+
stringified = subject.stringify how: { when: now }
|
83
|
+
|
84
|
+
expect(stringified).to eq %({"how":{"when":{"$date":#{now_js_epoch}}}})
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'marshals multiple dates' do
|
88
|
+
stringified = subject.stringify when: now, then: now
|
89
|
+
|
90
|
+
expect(stringified.gsub(' ', '')).to eq(
|
91
|
+
%({"when":{"$date":#{now_js_epoch}},"then":{"$date":#{now_js_epoch}}})
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'escapes reserved key when type is incorrect' do
|
96
|
+
stringified = subject.stringify '$date' => 'something'
|
97
|
+
|
98
|
+
expect(stringified).to eq '{"$escape":{"$date":"something"}}'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -39,4 +39,20 @@ describe Promise do
|
|
39
39
|
|
40
40
|
expect(count_occurences(b.inspect, 'Promise')).to eq(1)
|
41
41
|
end
|
42
|
+
|
43
|
+
it 'should not respond to comparitors' do
|
44
|
+
[:>, :<].each do |comp|
|
45
|
+
a = Promise.new
|
46
|
+
expect do
|
47
|
+
a.send(comp, 5)
|
48
|
+
end.to raise_error(NoMethodError)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should proxy methods on promises' do
|
53
|
+
a = Promise.new
|
54
|
+
expect do
|
55
|
+
a.something
|
56
|
+
end.not_to raise_error
|
57
|
+
end
|
42
58
|
end
|
@@ -19,6 +19,7 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_development_dependency "volt", "~> <%= config[:volt_version_base] %>"
|
22
|
+
spec.add_development_dependency 'rspec', '~> 3.2.0'
|
22
23
|
spec.add_development_dependency "rake"
|
23
24
|
<% if config[:test] -%>
|
24
25
|
spec.add_development_dependency "<%=config[:test]%>"
|
@@ -1,2 +1,14 @@
|
|
1
|
-
|
2
|
-
require '
|
1
|
+
# Volt sets up rspec and capybara for testing.
|
2
|
+
require 'volt/spec/setup'
|
3
|
+
Volt.spec_setup
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.run_all_when_everything_filtered = true
|
7
|
+
config.filter_run :focus
|
8
|
+
|
9
|
+
# Run specs in random order to surface order dependencies. If you find an
|
10
|
+
# order dependency and want to debug it, you can fix the order by providing
|
11
|
+
# the seed, which is printed after each run.
|
12
|
+
# --seed 1234
|
13
|
+
config.order = 'random'
|
14
|
+
end
|
@@ -1,10 +1,9 @@
|
|
1
|
-
<%# IMPORTANT: Please read before changing! %>
|
2
|
-
<%# This file is rendered on the server using ERB, so it does NOT use Volt's %>
|
3
|
-
<%# normal template system. You can add to it, but keep in mind the template %>
|
4
|
-
<%# language difference. This file handles auto-loading all JS/Opal and CSS. %>
|
5
|
-
|
6
1
|
<!DOCTYPE html>
|
7
2
|
<html>
|
3
|
+
<%# IMPORTANT: Please read before changing! %>
|
4
|
+
<%# This file is rendered on the server using ERB, so it does NOT use Volt's %>
|
5
|
+
<%# normal template system. You can add to it, but keep in mind the template %>
|
6
|
+
<%# language difference. This file handles auto-loading all JS/Opal and CSS. %>
|
8
7
|
<head>
|
9
8
|
<meta charset="UTF-8" />
|
10
9
|
<% javascript_files.each do |javascript_file| %>
|
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.3.
|
4
|
+
version: 0.9.3.pre6
|
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-06-
|
11
|
+
date: 2015-06-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -526,6 +526,7 @@ files:
|
|
526
526
|
- lib/volt/server/component_handler.rb
|
527
527
|
- lib/volt/server/component_templates.rb
|
528
528
|
- lib/volt/server/forking_server.rb
|
529
|
+
- lib/volt/server/forking_server/boot_error.html.erb
|
529
530
|
- lib/volt/server/html_parser/attribute_scope.rb
|
530
531
|
- lib/volt/server/html_parser/component_view_scope.rb
|
531
532
|
- lib/volt/server/html_parser/each_scope.rb
|
@@ -720,6 +721,7 @@ files:
|
|
720
721
|
- spec/tasks/query_tracker_spec.rb
|
721
722
|
- spec/tasks/user_tasks_spec.rb
|
722
723
|
- spec/templates/targets/binding_document/component_node_spec.rb
|
724
|
+
- spec/utils/ejson_spec.rb
|
723
725
|
- spec/utils/generic_counting_pool_spec.rb
|
724
726
|
- spec/utils/generic_pool_spec.rb
|
725
727
|
- spec/utils/parsing_spec.rb
|
@@ -964,6 +966,7 @@ test_files:
|
|
964
966
|
- spec/tasks/query_tracker_spec.rb
|
965
967
|
- spec/tasks/user_tasks_spec.rb
|
966
968
|
- spec/templates/targets/binding_document/component_node_spec.rb
|
969
|
+
- spec/utils/ejson_spec.rb
|
967
970
|
- spec/utils/generic_counting_pool_spec.rb
|
968
971
|
- spec/utils/generic_pool_spec.rb
|
969
972
|
- spec/utils/parsing_spec.rb
|