volt 0.8.18 → 0.8.19

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/Readme.md +2 -1
  4. data/VERSION +1 -1
  5. data/app/volt/controllers/notices_controller.rb +12 -2
  6. data/app/volt/models/user.rb +34 -1
  7. data/app/volt/tasks/store_tasks.rb +26 -23
  8. data/app/volt/tasks/user_tasks.rb +26 -2
  9. data/app/volt/views/notices/index.html +8 -6
  10. data/lib/volt.rb +47 -1
  11. data/lib/volt/cli/asset_compile.rb +24 -42
  12. data/lib/volt/config.rb +24 -14
  13. data/lib/volt/controllers/model_controller.rb +6 -1
  14. data/lib/volt/data_stores/mongo_driver.rb +7 -3
  15. data/lib/volt/models.rb +3 -0
  16. data/lib/volt/models/array_model.rb +21 -3
  17. data/lib/volt/models/model.rb +109 -48
  18. data/lib/volt/models/model_hash_behaviour.rb +25 -8
  19. data/lib/volt/models/persistors/array_store.rb +5 -2
  20. data/lib/volt/models/persistors/cookies.rb +91 -0
  21. data/lib/volt/models/persistors/model_store.rb +52 -14
  22. data/lib/volt/models/persistors/query/query_listener.rb +4 -1
  23. data/lib/volt/models/persistors/query/query_listener_pool.rb +1 -1
  24. data/lib/volt/models/persistors/store_state.rb +5 -5
  25. data/lib/volt/models/validations.rb +87 -18
  26. data/lib/volt/models/validators/length_validator.rb +1 -1
  27. data/lib/volt/models/validators/presence_validator.rb +1 -1
  28. data/lib/volt/models/validators/unique_validator.rb +28 -0
  29. data/lib/volt/page/bindings/template_binding.rb +2 -2
  30. data/lib/volt/page/page.rb +4 -0
  31. data/lib/volt/page/tasks.rb +2 -2
  32. data/lib/volt/reactive/computation.rb +2 -0
  33. data/lib/volt/reactive/hash_dependency.rb +1 -3
  34. data/lib/volt/reactive/reactive_array.rb +7 -0
  35. data/lib/volt/reactive/reactive_hash.rb +17 -0
  36. data/lib/volt/server.rb +1 -1
  37. data/lib/volt/server/component_templates.rb +3 -6
  38. data/lib/volt/server/html_parser/view_scope.rb +1 -1
  39. data/lib/volt/server/rack/component_code.rb +3 -2
  40. data/lib/volt/server/rack/component_paths.rb +15 -0
  41. data/lib/volt/server/rack/index_files.rb +1 -1
  42. data/lib/volt/tasks/dispatcher.rb +26 -12
  43. data/lib/volt/tasks/task_handler.rb +17 -15
  44. data/spec/apps/kitchen_sink/app/main/config/routes.rb +3 -0
  45. data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +26 -0
  46. data/spec/apps/kitchen_sink/app/main/controllers/users_test_controller.rb +27 -0
  47. data/spec/apps/kitchen_sink/app/main/views/main/cookie_test.html +25 -0
  48. data/spec/apps/kitchen_sink/app/main/views/main/flash.html +13 -0
  49. data/spec/apps/kitchen_sink/app/main/views/main/main.html +3 -0
  50. data/spec/apps/kitchen_sink/app/main/views/users_test/index.html +22 -0
  51. data/spec/apps/kitchen_sink/config/app.rb +3 -0
  52. data/spec/integration/bindings_spec.rb +1 -1
  53. data/spec/integration/cookies_spec.rb +48 -0
  54. data/spec/integration/flash_spec.rb +32 -0
  55. data/spec/models/model_spec.rb +3 -4
  56. data/spec/models/validations_spec.rb +13 -13
  57. data/spec/tasks/dispatcher_spec.rb +1 -1
  58. data/templates/project/config/app.rb.tt +6 -1
  59. data/templates/project/{public → config/base_page}/index.html +0 -0
  60. data/volt.gemspec +4 -0
  61. 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
@@ -122,7 +122,7 @@ module Volt
122
122
 
123
123
  @app.use Rack::Static,
124
124
  urls: ['/'],
125
- root: 'public',
125
+ root: 'config/base',
126
126
  index: '',
127
127
  header_rules: [
128
128
  [:all, { 'Cache-Control' => 'public, max-age=86400' }]
@@ -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 = true
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
- if @client
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].gsub(':', '/')
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
- def code
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, @client).code
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
 
@@ -34,7 +34,7 @@ module Volt
34
34
  end
35
35
 
36
36
  def html
37
- index_path = File.expand_path(File.join(Volt.root, 'public/index.html'))
37
+ index_path = File.expand_path(File.join(Volt.root, 'config/base/index.html'))
38
38
  html = File.read(index_path)
39
39
 
40
40
  ERB.new(html).result(binding)
@@ -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
- result = nil
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
- begin
24
+ promise = promise.then do
25
+ Thread.current['meta'] = meta_data
26
+
18
27
  result = klass.new(channel, self).send(method_name, *args)
19
- rescue => e
20
- # TODO: Log these errors better
21
- puts e.inspect
22
- puts e.backtrace
23
- error = e
28
+
29
+ Thread.current['meta'] = nil
30
+
31
+ result
24
32
  end
33
+
25
34
  else
26
35
  # Unsafe method
27
- error = RuntimeError.new("unsafe method: #{method_name}")
36
+ promise.reject(RuntimeError.new("unsafe method: #{method_name}"))
28
37
  end
29
38
 
30
39
  if callback_id
31
- # Callback with result
32
- channel.send_message('response', callback_id, result, error)
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
- $page.tasks.call(self.name, name, *args, &block)
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 promises.
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
- promise = Promise.new
28
-
29
- begin
30
- result = new(nil, nil).send(name, *args, &block)
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,3 @@
1
+ Volt.setup do |config|
2
+ config.app_secret = 'vFYUzMiPIdMw1Iox0ggDpBYorLm_d55YtRPc0eGrdCpoN4h9E5FcWySIT_D8JIEOllU'
3
+ end
@@ -188,7 +188,7 @@ if ENV['BROWSER']
188
188
  expect(find('#paramsCheck3')).to have_content('')
189
189
 
190
190
  if ENV['BROWSER'] != 'phantom'
191
- expect(current_url).to match(/\/bindings$/)
191
+ expect(current_url).to match(/\/bindings[?]check[=]false$/)
192
192
  end
193
193
  end
194
194
  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