volt 0.9.3.pre5 → 0.9.3.pre6
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 +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
|