volt 0.8.18 → 0.8.19
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 +14 -0
- data/Readme.md +2 -1
- data/VERSION +1 -1
- data/app/volt/controllers/notices_controller.rb +12 -2
- data/app/volt/models/user.rb +34 -1
- data/app/volt/tasks/store_tasks.rb +26 -23
- data/app/volt/tasks/user_tasks.rb +26 -2
- data/app/volt/views/notices/index.html +8 -6
- data/lib/volt.rb +47 -1
- data/lib/volt/cli/asset_compile.rb +24 -42
- data/lib/volt/config.rb +24 -14
- data/lib/volt/controllers/model_controller.rb +6 -1
- data/lib/volt/data_stores/mongo_driver.rb +7 -3
- data/lib/volt/models.rb +3 -0
- data/lib/volt/models/array_model.rb +21 -3
- data/lib/volt/models/model.rb +109 -48
- data/lib/volt/models/model_hash_behaviour.rb +25 -8
- data/lib/volt/models/persistors/array_store.rb +5 -2
- data/lib/volt/models/persistors/cookies.rb +91 -0
- data/lib/volt/models/persistors/model_store.rb +52 -14
- data/lib/volt/models/persistors/query/query_listener.rb +4 -1
- data/lib/volt/models/persistors/query/query_listener_pool.rb +1 -1
- data/lib/volt/models/persistors/store_state.rb +5 -5
- data/lib/volt/models/validations.rb +87 -18
- data/lib/volt/models/validators/length_validator.rb +1 -1
- data/lib/volt/models/validators/presence_validator.rb +1 -1
- data/lib/volt/models/validators/unique_validator.rb +28 -0
- data/lib/volt/page/bindings/template_binding.rb +2 -2
- data/lib/volt/page/page.rb +4 -0
- data/lib/volt/page/tasks.rb +2 -2
- data/lib/volt/reactive/computation.rb +2 -0
- data/lib/volt/reactive/hash_dependency.rb +1 -3
- data/lib/volt/reactive/reactive_array.rb +7 -0
- data/lib/volt/reactive/reactive_hash.rb +17 -0
- data/lib/volt/server.rb +1 -1
- data/lib/volt/server/component_templates.rb +3 -6
- data/lib/volt/server/html_parser/view_scope.rb +1 -1
- data/lib/volt/server/rack/component_code.rb +3 -2
- data/lib/volt/server/rack/component_paths.rb +15 -0
- data/lib/volt/server/rack/index_files.rb +1 -1
- data/lib/volt/tasks/dispatcher.rb +26 -12
- data/lib/volt/tasks/task_handler.rb +17 -15
- data/spec/apps/kitchen_sink/app/main/config/routes.rb +3 -0
- data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +26 -0
- data/spec/apps/kitchen_sink/app/main/controllers/users_test_controller.rb +27 -0
- data/spec/apps/kitchen_sink/app/main/views/main/cookie_test.html +25 -0
- data/spec/apps/kitchen_sink/app/main/views/main/flash.html +13 -0
- data/spec/apps/kitchen_sink/app/main/views/main/main.html +3 -0
- data/spec/apps/kitchen_sink/app/main/views/users_test/index.html +22 -0
- data/spec/apps/kitchen_sink/config/app.rb +3 -0
- data/spec/integration/bindings_spec.rb +1 -1
- data/spec/integration/cookies_spec.rb +48 -0
- data/spec/integration/flash_spec.rb +32 -0
- data/spec/models/model_spec.rb +3 -4
- data/spec/models/validations_spec.rb +13 -13
- data/spec/tasks/dispatcher_spec.rb +1 -1
- data/templates/project/config/app.rb.tt +6 -1
- data/templates/project/{public → config/base_page}/index.html +0 -0
- data/volt.gemspec +4 -0
- metadata +47 -3
@@ -25,6 +25,11 @@ module Volt
|
|
25
25
|
@array.each(&block)
|
26
26
|
end
|
27
27
|
|
28
|
+
def empty?
|
29
|
+
@size_dep.depend
|
30
|
+
@array.empty?
|
31
|
+
end
|
32
|
+
|
28
33
|
def count(&block)
|
29
34
|
if block
|
30
35
|
count = 0
|
@@ -220,6 +225,8 @@ module Volt
|
|
220
225
|
end
|
221
226
|
|
222
227
|
def inspect
|
228
|
+
# TODO: should also depend on items in array
|
229
|
+
@size_dep.depend
|
223
230
|
"#<#{self.class}:#{object_id} #{@array.inspect}>"
|
224
231
|
end
|
225
232
|
|
@@ -35,6 +35,7 @@ module Volt
|
|
35
35
|
|
36
36
|
def delete(key)
|
37
37
|
@deps.delete(key)
|
38
|
+
@all_deps.changed!
|
38
39
|
@hash.delete(key)
|
39
40
|
end
|
40
41
|
|
@@ -42,9 +43,25 @@ module Volt
|
|
42
43
|
@hash.each_pair do |key, _|
|
43
44
|
delete(key)
|
44
45
|
end
|
46
|
+
|
47
|
+
@all_deps.changed!
|
48
|
+
end
|
49
|
+
|
50
|
+
def replace(hash)
|
51
|
+
clear
|
52
|
+
|
53
|
+
hash.each_pair do |key, value|
|
54
|
+
self[key] = value
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_h
|
59
|
+
@all_deps.depend
|
60
|
+
@hash
|
45
61
|
end
|
46
62
|
|
47
63
|
def inspect
|
64
|
+
@all_deps.depend
|
48
65
|
"#<ReactiveHash #{@hash.inspect}>"
|
49
66
|
end
|
50
67
|
end
|
data/lib/volt/server.rb
CHANGED
@@ -4,10 +4,11 @@ require 'volt/server/html_parser/view_parser'
|
|
4
4
|
# setup code (for controllers, models, views, and routes)
|
5
5
|
module Volt
|
6
6
|
class ComponentTemplates
|
7
|
+
# client is if we are generating for the client or backend
|
7
8
|
def initialize(component_path, component_name, client = true)
|
8
9
|
@component_path = component_path
|
9
10
|
@component_name = component_name
|
10
|
-
@client =
|
11
|
+
@client = client
|
11
12
|
end
|
12
13
|
|
13
14
|
def code
|
@@ -21,11 +22,7 @@ module Volt
|
|
21
22
|
end
|
22
23
|
|
23
24
|
def page_reference
|
24
|
-
|
25
|
-
'$page'
|
26
|
-
else
|
27
|
-
'page'
|
28
|
-
end
|
25
|
+
'$page'
|
29
26
|
end
|
30
27
|
|
31
28
|
def generate_view_code
|
@@ -107,7 +107,7 @@ module Volt
|
|
107
107
|
end
|
108
108
|
|
109
109
|
def add_component(tag_name, attributes, unary)
|
110
|
-
component_name = tag_name[1..-1].
|
110
|
+
component_name = tag_name[1..-1].tr(':', '/')
|
111
111
|
|
112
112
|
@handler.html << "<!-- $#{@binding_number} --><!-- $/#{@binding_number} -->"
|
113
113
|
|
@@ -12,12 +12,13 @@ module Volt
|
|
12
12
|
@client = client
|
13
13
|
end
|
14
14
|
|
15
|
-
|
15
|
+
# The client argument is for if this code is being generated for the client
|
16
|
+
def code(client=true)
|
16
17
|
code = ''
|
17
18
|
|
18
19
|
asset_files = AssetFiles.new(@component_name, @component_paths)
|
19
20
|
asset_files.component_paths.each do |component_path, component_name|
|
20
|
-
code << ComponentTemplates.new(component_path, component_name,
|
21
|
+
code << ComponentTemplates.new(component_path, component_name, client).code
|
21
22
|
code << "\n\n"
|
22
23
|
end
|
23
24
|
|
@@ -1,4 +1,6 @@
|
|
1
1
|
# ComponentPaths gives an array of every folder where you find a component.
|
2
|
+
require 'volt/server/rack/component_code'
|
3
|
+
|
2
4
|
module Volt
|
3
5
|
class ComponentPaths
|
4
6
|
def initialize(root = nil)
|
@@ -73,6 +75,19 @@ module Volt
|
|
73
75
|
end
|
74
76
|
end
|
75
77
|
end
|
78
|
+
|
79
|
+
# component_names = []
|
80
|
+
# app_folders do |app_folder|
|
81
|
+
# Dir["#{app_folder}/*"].map {|cp| cp[/[^\/]+$/] }.each do |component_name|
|
82
|
+
# component_names << component_name
|
83
|
+
# end
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# # Load in all views
|
87
|
+
# component_names.uniq.each do |component_name|
|
88
|
+
# code = Volt::ComponentCode.new(component_name, self).code
|
89
|
+
# eval(code)
|
90
|
+
# end
|
76
91
|
end
|
77
92
|
end
|
78
93
|
|
@@ -2,34 +2,48 @@ module Volt
|
|
2
2
|
# The task dispatcher is responsible for taking incoming messages
|
3
3
|
# from the socket channel and dispatching them to the proper handler.
|
4
4
|
class Dispatcher
|
5
|
+
|
6
|
+
# Dispatch takes an incoming Task from the client and runs it on the
|
7
|
+
# server, returning the result to the client.
|
8
|
+
# Tasks returning a promise will wait to return.
|
5
9
|
def dispatch(channel, message)
|
6
|
-
callback_id, class_name, method_name, *args = message
|
10
|
+
callback_id, class_name, method_name, meta_data, *args = message
|
7
11
|
method_name = method_name.to_sym
|
8
12
|
|
9
13
|
# Get the class
|
10
14
|
klass = Object.send(:const_get, class_name)
|
11
15
|
|
12
|
-
|
13
|
-
error = nil
|
16
|
+
promise = Promise.new
|
14
17
|
|
18
|
+
# Check that we are calling on a TaskHandler class and a method provide at
|
19
|
+
# TaskHandler or above in the ancestor chain.
|
15
20
|
if safe_method?(klass, method_name)
|
21
|
+
promise.resolve(nil)
|
22
|
+
|
16
23
|
# Init and send the method
|
17
|
-
|
24
|
+
promise = promise.then do
|
25
|
+
Thread.current['meta'] = meta_data
|
26
|
+
|
18
27
|
result = klass.new(channel, self).send(method_name, *args)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
error = e
|
28
|
+
|
29
|
+
Thread.current['meta'] = nil
|
30
|
+
|
31
|
+
result
|
24
32
|
end
|
33
|
+
|
25
34
|
else
|
26
35
|
# Unsafe method
|
27
|
-
|
36
|
+
promise.reject(RuntimeError.new("unsafe method: #{method_name}"))
|
28
37
|
end
|
29
38
|
|
30
39
|
if callback_id
|
31
|
-
#
|
32
|
-
|
40
|
+
# Run the promise and pass the return value/error back to the client
|
41
|
+
promise.then do |result|
|
42
|
+
channel.send_message('response', callback_id, result, nil)
|
43
|
+
end.fail do |error|
|
44
|
+
channel.send_message('response', callback_id, nil, error)
|
45
|
+
Volt.logger.error(error)
|
46
|
+
end
|
33
47
|
end
|
34
48
|
end
|
35
49
|
|
@@ -4,7 +4,16 @@ module Volt
|
|
4
4
|
# On the front-end we setup a proxy class to the backend that returns
|
5
5
|
# promises for all calls.
|
6
6
|
def self.method_missing(name, *args, &block)
|
7
|
-
|
7
|
+
# Meta data is passed from the browser to the server so the server can know
|
8
|
+
# things like who's logged in.
|
9
|
+
meta_data = {}
|
10
|
+
|
11
|
+
user_id = $page.cookies._user_id
|
12
|
+
unless user_id.nil?
|
13
|
+
meta_data['user_id'] = user_id
|
14
|
+
end
|
15
|
+
|
16
|
+
$page.tasks.call(self.name, name, meta_data, *args, &block)
|
8
17
|
end
|
9
18
|
else
|
10
19
|
def initialize(channel = nil, dispatcher = nil)
|
@@ -22,21 +31,14 @@ module Volt
|
|
22
31
|
end
|
23
32
|
|
24
33
|
# On the backend, we proxy all class methods like we would
|
25
|
-
# on the front-end. This returns
|
34
|
+
# on the front-end. This returns a promise, even if the
|
35
|
+
# original code did not.
|
26
36
|
def self.method_missing(name, *args, &block)
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
promise.resolve(result)
|
33
|
-
rescue => e
|
34
|
-
puts "Task Error: #{e.inspect}"
|
35
|
-
puts e.backtrace
|
36
|
-
promise.reject(e)
|
37
|
-
end
|
38
|
-
|
39
|
-
promise
|
37
|
+
# TODO: optimize: this could run the inside first to see if it
|
38
|
+
# returns a promise, so we don't have to wrap it.
|
39
|
+
return Promise.new.then do
|
40
|
+
new(nil, nil).send(name, *args, &block)
|
41
|
+
end.resolve(nil)
|
40
42
|
end
|
41
43
|
|
42
44
|
# Provide access to the store collection
|
@@ -3,7 +3,10 @@
|
|
3
3
|
get '/bindings/{{_route_test}}', _action: 'bindings'
|
4
4
|
get '/bindings', _action: 'bindings'
|
5
5
|
get '/store', _action: 'store'
|
6
|
+
get '/cookie_test', _action: 'cookie_test'
|
7
|
+
get '/flash', _action: 'flash'
|
6
8
|
get '/todos', _controller: 'todos'
|
9
|
+
get '/users_test', _controller: 'users_test'
|
7
10
|
|
8
11
|
# The main route, this should be last. It will match any params not previously matched.
|
9
12
|
get '/', {}
|
@@ -1,6 +1,32 @@
|
|
1
1
|
class MainController < Volt::ModelController
|
2
2
|
model :page
|
3
3
|
|
4
|
+
def flash_notice
|
5
|
+
flash._notices << 'A notice message'
|
6
|
+
end
|
7
|
+
|
8
|
+
def flash_success
|
9
|
+
flash._successes << 'A success message'
|
10
|
+
end
|
11
|
+
|
12
|
+
def flash_warning
|
13
|
+
flash._warnings << 'A warning message'
|
14
|
+
end
|
15
|
+
|
16
|
+
def flash_error
|
17
|
+
flash._errors << 'An error message'
|
18
|
+
end
|
19
|
+
|
20
|
+
def cookie_test
|
21
|
+
self.model = page._new_cookie.buffer
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_cookie
|
25
|
+
cookies.send(:"_#{_name.to_s}=", _value)
|
26
|
+
|
27
|
+
self.model = page._new_cookie.buffer
|
28
|
+
end
|
29
|
+
|
4
30
|
private
|
5
31
|
|
6
32
|
# the main template contains a #template binding that shows another
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class UsersTestController < Volt::ModelController
|
2
|
+
def index
|
3
|
+
self.model = store._users.buffer
|
4
|
+
end
|
5
|
+
|
6
|
+
def signup
|
7
|
+
puts "Signup"
|
8
|
+
model.save!.then do |a|
|
9
|
+
puts "Saved"
|
10
|
+
end.fail do |err|
|
11
|
+
puts "Fail with: #{err.inspect}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def login
|
16
|
+
puts "USER LOGIN"
|
17
|
+
User.login('ryanstout@gmail.com', 'temppass').then do |result|
|
18
|
+
puts "Login Success"
|
19
|
+
end.fail do |err|
|
20
|
+
puts "ERR: #{err.inspect}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def logout
|
25
|
+
User.logout
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<:Title>
|
2
|
+
Cookies
|
3
|
+
|
4
|
+
<:Body>
|
5
|
+
<h1>Add Cookies</h1>
|
6
|
+
|
7
|
+
<form e-submit="add_cookie">
|
8
|
+
<div class="form-group">
|
9
|
+
<label>Name</label>
|
10
|
+
<input class="form-control" id="cookieName" type="text" value="{{ _name }}" />
|
11
|
+
</div>
|
12
|
+
<div class="form-group">
|
13
|
+
<label>Value</label>
|
14
|
+
<input class="form-control" id="cookieValue" type="text" value="{{ _value }}" />
|
15
|
+
</div>
|
16
|
+
<button>Add Cookie</button>
|
17
|
+
</form>
|
18
|
+
|
19
|
+
<h2>Cookies</h2>
|
20
|
+
|
21
|
+
<ul>
|
22
|
+
{{ cookies.keys.each do |key| }}
|
23
|
+
<li>{{ key }}: {{ cookies.read_attribute(key) }} <button class="cookieDelete" e-click="cookies.delete(key)">X</button></li>
|
24
|
+
{{ end }}
|
25
|
+
</ul>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<:Title>
|
2
|
+
Flash Messages
|
3
|
+
|
4
|
+
<:Body>
|
5
|
+
<h1>Flash Messages</h1>
|
6
|
+
|
7
|
+
<p>
|
8
|
+
<a e-click="flash_notice">Flash Notice</a><br />
|
9
|
+
<a e-click="flash_success">Flash Success</a><br />
|
10
|
+
<a e-click="flash_warning">Flash Warning</a><br />
|
11
|
+
<a e-click="flash_error">Flash Error</a><br />
|
12
|
+
</p>
|
13
|
+
|
@@ -8,7 +8,10 @@
|
|
8
8
|
<:nav href="/" text="Home" />
|
9
9
|
<:nav href="/bindings" text="Bindings" />
|
10
10
|
<:nav href="/todos" text="Todos" />
|
11
|
+
<:nav href="/flash" text="Flash" />
|
12
|
+
<:nav href="/cookie_test" text="Cookies" />
|
11
13
|
<:nav href="/store" text="Store" />
|
14
|
+
<:nav href="/users_test" text="Users" />
|
12
15
|
</ul>
|
13
16
|
<h3 class="text-muted">Project name</h3>
|
14
17
|
</div>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<:Title>
|
2
|
+
Users Test
|
3
|
+
|
4
|
+
<:Body>
|
5
|
+
<h1>Users Test</h1>
|
6
|
+
|
7
|
+
<form e-submit="signup">
|
8
|
+
{{ errors.html_inspect }}<br />
|
9
|
+
<input value="{{ _username }}" /><br />
|
10
|
+
<input type="password" value="{{ _password }}" /><br />
|
11
|
+
|
12
|
+
<button>Signup</button>
|
13
|
+
</form>
|
14
|
+
|
15
|
+
<hr />
|
16
|
+
|
17
|
+
<button e-click="login">Login</button><button e-click="logout">Logout</button>
|
18
|
+
|
19
|
+
{{ if Volt.user }}
|
20
|
+
{{ Volt.user._username }}<br />
|
21
|
+
{{ Volt.user._name }}
|
22
|
+
{{ end }}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
if ENV['BROWSER']
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe 'cookies collection', type: :feature do
|
5
|
+
if ENV['BROWSER'] != 'phantom'
|
6
|
+
# TODO: fails in phantom for some reason
|
7
|
+
it 'should add' do
|
8
|
+
visit '/'
|
9
|
+
|
10
|
+
click_link 'Cookies'
|
11
|
+
|
12
|
+
fill_in('cookieName', with: 'one')
|
13
|
+
fill_in('cookieValue', with: 'one')
|
14
|
+
click_button 'Add Cookie'
|
15
|
+
|
16
|
+
expect(page).to have_content('one: one')
|
17
|
+
|
18
|
+
# Reload the page
|
19
|
+
page.evaluate_script('document.location.reload()')
|
20
|
+
|
21
|
+
# Check again
|
22
|
+
expect(page).to have_content('one: one')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should delete cookies' do
|
27
|
+
visit '/'
|
28
|
+
|
29
|
+
click_link 'Cookies'
|
30
|
+
|
31
|
+
fill_in('cookieName', with: 'two')
|
32
|
+
fill_in('cookieValue', with: 'two')
|
33
|
+
click_button 'Add Cookie'
|
34
|
+
|
35
|
+
expect(page).to have_content('two: two')
|
36
|
+
|
37
|
+
find('.cookieDelete').click
|
38
|
+
|
39
|
+
expect(page).to_not have_content('two: two')
|
40
|
+
|
41
|
+
# Reload the page
|
42
|
+
page.evaluate_script('document.location.reload()')
|
43
|
+
|
44
|
+
expect(page).to_not have_content('two: two')
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|