volt 0.7.1 → 0.7.2

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.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -2
  3. data/Readme.md +97 -56
  4. data/VERSION +1 -1
  5. data/app/volt/assets/js/sockjs-0.3.4.min.js +27 -0
  6. data/app/volt/assets/js/vertxbus.js +216 -0
  7. data/app/volt/tasks/live_query/live_query.rb +5 -5
  8. data/app/volt/tasks/live_query/live_query_pool.rb +1 -1
  9. data/app/volt/tasks/query_tasks.rb +5 -0
  10. data/app/volt/tasks/store_tasks.rb +44 -18
  11. data/docs/WHY.md +10 -0
  12. data/lib/volt/cli.rb +18 -7
  13. data/lib/volt/controllers/model_controller.rb +30 -8
  14. data/lib/volt/extra_core/inflections.rb +63 -0
  15. data/lib/volt/extra_core/inflector/inflections.rb +203 -0
  16. data/lib/volt/extra_core/inflector/methods.rb +63 -0
  17. data/lib/volt/extra_core/inflector.rb +4 -0
  18. data/lib/volt/extra_core/object.rb +9 -0
  19. data/lib/volt/extra_core/string.rb +10 -14
  20. data/lib/volt/models/array_model.rb +45 -27
  21. data/lib/volt/models/cursor.rb +6 -0
  22. data/lib/volt/models/model.rb +127 -12
  23. data/lib/volt/models/model_hash_behaviour.rb +8 -5
  24. data/lib/volt/models/model_helpers.rb +4 -4
  25. data/lib/volt/models/model_state.rb +22 -0
  26. data/lib/volt/models/persistors/array_store.rb +49 -35
  27. data/lib/volt/models/persistors/base.rb +3 -3
  28. data/lib/volt/models/persistors/model_store.rb +17 -6
  29. data/lib/volt/models/persistors/query/query_listener.rb +0 -2
  30. data/lib/volt/models/persistors/store.rb +0 -4
  31. data/lib/volt/models/persistors/store_state.rb +27 -0
  32. data/lib/volt/models/url.rb +2 -2
  33. data/lib/volt/models/validations/errors.rb +0 -0
  34. data/lib/volt/models/validations/length.rb +13 -0
  35. data/lib/volt/models/validations/validations.rb +82 -0
  36. data/lib/volt/models.rb +1 -1
  37. data/lib/volt/page/bindings/attribute_binding.rb +29 -14
  38. data/lib/volt/page/bindings/base_binding.rb +2 -2
  39. data/lib/volt/page/bindings/component_binding.rb +29 -25
  40. data/lib/volt/page/bindings/content_binding.rb +1 -0
  41. data/lib/volt/page/bindings/each_binding.rb +25 -33
  42. data/lib/volt/page/bindings/event_binding.rb +0 -1
  43. data/lib/volt/page/bindings/if_binding.rb +3 -1
  44. data/lib/volt/page/bindings/template_binding.rb +61 -28
  45. data/lib/volt/page/document_events.rb +3 -1
  46. data/lib/volt/page/draw_cycle.rb +22 -0
  47. data/lib/volt/page/page.rb +10 -1
  48. data/lib/volt/page/reactive_template.rb +23 -16
  49. data/lib/volt/page/sub_context.rb +1 -1
  50. data/lib/volt/page/targets/attribute_section.rb +3 -2
  51. data/lib/volt/page/targets/attribute_target.rb +0 -4
  52. data/lib/volt/page/targets/base_section.rb +25 -0
  53. data/lib/volt/page/targets/binding_document/component_node.rb +13 -14
  54. data/lib/volt/page/targets/binding_document/html_node.rb +4 -0
  55. data/lib/volt/page/targets/dom_section.rb +16 -67
  56. data/lib/volt/page/targets/dom_template.rb +99 -0
  57. data/lib/volt/page/targets/helpers/comment_searchers.rb +29 -0
  58. data/lib/volt/page/template_renderer.rb +2 -14
  59. data/lib/volt/reactive/array_extensions.rb +0 -1
  60. data/lib/volt/reactive/event_chain.rb +9 -2
  61. data/lib/volt/reactive/events.rb +44 -37
  62. data/lib/volt/reactive/object_tracking.rb +1 -1
  63. data/lib/volt/reactive/reactive_array.rb +18 -0
  64. data/lib/volt/reactive/reactive_count.rb +108 -0
  65. data/lib/volt/reactive/reactive_generator.rb +44 -0
  66. data/lib/volt/reactive/reactive_value.rb +73 -73
  67. data/lib/volt/reactive/string_extensions.rb +1 -1
  68. data/lib/volt/router/routes.rb +205 -88
  69. data/lib/volt/server/component_handler.rb +3 -1
  70. data/lib/volt/server/html_parser/view_parser.rb +20 -4
  71. data/lib/volt/server/rack/component_paths.rb +13 -10
  72. data/lib/volt/server/rack/index_files.rb +4 -4
  73. data/lib/volt/server/socket_connection_handler.rb +5 -1
  74. data/lib/volt/server.rb +10 -3
  75. data/spec/apps/kitchen_sink/.gitignore +8 -0
  76. data/spec/apps/kitchen_sink/Gemfile +32 -0
  77. data/spec/apps/kitchen_sink/app/home/views/index/index.html +3 -5
  78. data/spec/apps/kitchen_sink/config.ru +4 -0
  79. data/spec/apps/kitchen_sink/public/index.html +2 -2
  80. data/spec/extra_core/inflector_spec.rb +8 -0
  81. data/spec/models/event_chain_spec.rb +18 -0
  82. data/spec/models/model_buffers_spec.rb +9 -0
  83. data/spec/models/model_spec.rb +22 -9
  84. data/spec/models/reactive_array_spec.rb +26 -1
  85. data/spec/models/reactive_call_times_spec.rb +28 -0
  86. data/spec/models/reactive_value_spec.rb +19 -0
  87. data/spec/models/validations_spec.rb +39 -0
  88. data/spec/page/bindings/content_binding_spec.rb +1 -0
  89. data/spec/{templates → page/bindings}/template_binding_spec.rb +54 -0
  90. data/spec/router/routes_spec.rb +156 -8
  91. data/spec/server/html_parser/sandlebars_parser_spec.rb +55 -47
  92. data/spec/server/html_parser/view_parser_spec.rb +3 -0
  93. data/spec/server/rack/asset_files_spec.rb +1 -1
  94. data/spec/spec_helper.rb +25 -11
  95. data/spec/templates/targets/binding_document/component_node_spec.rb +12 -0
  96. data/templates/project/Gemfile.tt +11 -0
  97. data/templates/project/app/home/config/routes.rb +1 -1
  98. data/templates/project/app/home/controllers/index_controller.rb +5 -5
  99. data/templates/project/app/home/views/index/index.html +6 -6
  100. data/volt.gemspec +5 -6
  101. metadata +34 -76
  102. data/app/volt/assets/js/sockjs-0.2.1.min.js +0 -27
  103. data/lib/volt/reactive/object_tracker.rb +0 -107
@@ -8,7 +8,8 @@ class ComponentPaths
8
8
  def app_folders
9
9
  # Find all app folders
10
10
  @app_folders ||= begin
11
- app_folders = ["#{@root}/app", "#{@root}/vendor/app"].map {|f| File.expand_path(f) }
11
+ volt_app = File.expand_path(File.join(File.dirname(__FILE__), "../../../../app"))
12
+ app_folders = [volt_app, "#{@root}/app", "#{@root}/vendor/app"].map {|f| File.expand_path(f) }
12
13
 
13
14
  # Gem folders with volt in them
14
15
  # TODO: we should probably qualify this a bit more
@@ -49,15 +50,17 @@ class ComponentPaths
49
50
 
50
51
  # Makes each components classes available on the load path, require classes.
51
52
  def require_in_components
52
- # app_folders do |app_folder|
53
- # $LOAD_PATH.unshift(app_folder)
54
- #
55
- # Dir["#{app_folder}/*/{controllers,model}/*.rb"].each do |ruby_file|
56
- # path = ruby_file.gsub(/^#{app_folder}\//, '')[0..-4]
57
- # puts "Path: #{path}"
58
- # # require(path)
59
- # end
60
- # end
53
+ if RUBY_PLATFORM == 'opal'
54
+ else
55
+ app_folders do |app_folder|
56
+ $LOAD_PATH.unshift(app_folder)
57
+
58
+ Dir["#{app_folder}/*/{controllers,models}/*.rb"].each do |ruby_file|
59
+ path = ruby_file.gsub(/^#{app_folder}\//, '')[0..-4]
60
+ require(path)
61
+ end
62
+ end
63
+ end
61
64
 
62
65
  # add each tasks folder directly
63
66
  components.each do |name,component_folders|
@@ -17,16 +17,16 @@ class IndexFiles
17
17
  end
18
18
 
19
19
  def route_match?(path)
20
- @@router.path_matchers.each do |path_matcher|
21
- return true if path =~ path_matcher
22
- end
20
+ params = @@router.url_to_params(path)
21
+
22
+ return params if params
23
23
 
24
24
  return false
25
25
  end
26
26
 
27
27
  def call(env)
28
28
  if route_match?(env['PATH_INFO'])
29
- [200, { 'Content-Type' => 'text/html' }, [html]]
29
+ [200, { 'Content-Type' => 'text/html; charset=utf-8' }, [html]]
30
30
  else
31
31
  @app.call env
32
32
  end
@@ -43,7 +43,11 @@ class SocketConnectionHandler < SockJS::Session
43
43
  def send_message(*args)
44
44
  str = JSON.dump([*args])
45
45
 
46
- send(str)
46
+ begin
47
+ send(str)
48
+ rescue MetaState::WrongStateError => e
49
+ puts "Tried to send to closed connection: #{e.inspect}"
50
+ end
47
51
  end
48
52
 
49
53
  def closed
data/lib/volt/server.rb CHANGED
@@ -1,7 +1,12 @@
1
1
  ENV['SERVER'] = 'true'
2
2
 
3
3
  require 'opal'
4
- require 'thin'
4
+ if RUBY_PLATFORM == 'java'
5
+ require 'jubilee'
6
+ else
7
+ require 'thin'
8
+ end
9
+
5
10
  require "rack"
6
11
  if RUBY_PLATFORM != 'java'
7
12
  require "rack/sockjs"
@@ -20,6 +25,7 @@ require 'volt/server/rack/component_paths'
20
25
  require 'volt/server/rack/index_files'
21
26
  require 'volt/server/rack/opal_files'
22
27
  require 'volt/tasks/dispatcher'
28
+ require 'volt/page/page'
23
29
 
24
30
  module Rack
25
31
  # TODO: For some reason in Rack (or maybe thin), 304 headers close
@@ -96,9 +102,10 @@ class Server
96
102
  # which JS/CSS files to serve.
97
103
  @app.use IndexFiles, @component_paths, opal_files
98
104
 
105
+ component_paths.require_in_components
106
+
99
107
  # Handle socks js connection
100
108
  if RUBY_PLATFORM != 'java'
101
- component_paths.require_in_components
102
109
  SocketConnectionHandler.dispatcher = Dispatcher.new
103
110
 
104
111
  @app.map "/channel" do
@@ -114,7 +121,7 @@ class Server
114
121
  [:all, {'Cache-Control' => 'public, max-age=86400'}]
115
122
  ]
116
123
 
117
- @app.run lambda{ |env| [ 404, { 'Content-Type' => 'text/html' }, ['404 - page not found'] ] }
124
+ @app.run lambda{ |env| [ 404, { 'Content-Type' => 'text/html; charset=utf-8' }, ['404 - page not found'] ] }
118
125
 
119
126
  return @app
120
127
  end
@@ -0,0 +1,8 @@
1
+ .bundle
2
+ .config
3
+ .yardoc
4
+ tmp
5
+ .idea
6
+ .yardoc
7
+ .sass-cache
8
+ .DS_Store
@@ -0,0 +1,32 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'volt', path: '/Users/ryanstout/Sites/volt/volt'
4
+
5
+
6
+ # The following gem's are optional for themeing
7
+
8
+ # Twitter bootstrap
9
+ gem 'volt-bootstrap'
10
+
11
+ # Simple theme for bootstrap, remove to theme yourself.
12
+ gem 'volt-bootstrap-jumbotron-theme'
13
+
14
+
15
+ gem 'volt-fields', path: '/Users/ryanstout/Sites/volt/apps/volt-fields'
16
+
17
+ # Server for MRI
18
+ platform :mri do
19
+ gem 'thin', '~> 1.6.0'
20
+ gem 'bson_ext'
21
+ end
22
+
23
+ # Server for jruby
24
+ platform :jruby do
25
+ gem 'jubilee'
26
+ end
27
+
28
+ #---------------------
29
+ # Needed at the moment
30
+ gem 'opal', git: 'https://github.com/opal/opal.git'
31
+ gem 'opal-jquery', :git => 'https://github.com/opal/opal-jquery.git'
32
+ gem 'sockjs', git: 'https://github.com/kacperk/sockjs-ruby.git', require: false, platforms: :mri
@@ -1,7 +1,5 @@
1
- <:title>
2
- Home
3
- </:title>
1
+ <:Title>
2
+ Home
4
3
 
5
- <:body>
4
+ <:Body>
6
5
  <h1>Home</h1>
7
- </:body>
@@ -0,0 +1,4 @@
1
+ # Run via rack server
2
+ require 'bundler/setup'
3
+ require 'volt/server'
4
+ run Server.new.app
@@ -9,9 +9,9 @@
9
9
  <% css_files.each do |css_file| %>
10
10
  <link href="<%= css_file %>" media="all" rel="stylesheet" type="text/css" />
11
11
  <% end %>
12
-
12
+
13
13
  </head>
14
14
  <body>
15
-
15
+
16
16
  </body>
17
17
  </html>
@@ -0,0 +1,8 @@
1
+ require 'volt/extra_core/inflector'
2
+
3
+ describe Inflector do
4
+ it "should pluralize correctly" do
5
+ expect('car'.pluralize).to eq('cars')
6
+ # expect('database'.pluralize).to eq('database')
7
+ end
8
+ end
@@ -128,4 +128,22 @@ describe EventChain do
128
128
  expect(count).to eq(3)
129
129
  end
130
130
  end
131
+
132
+ it "should free events when events are unbound" do
133
+ a = ReactiveValue.new('cool')
134
+ b = a + 'dude'
135
+
136
+ expect(a.reactive_manager.event_chain.instance_variable_get('@event_counts')).to eq({})
137
+
138
+ listener1 = b.on('changed') { }
139
+ expect(a.reactive_manager.event_chain.instance_variable_get('@event_counts')).to_not eq({})
140
+
141
+ listener2 = b.on('changed') { }
142
+ listener1.remove
143
+
144
+ expect(a.reactive_manager.event_chain.instance_variable_get('@event_counts')).to_not eq({})
145
+ listener2.remove
146
+
147
+ expect(a.reactive_manager.event_chain.instance_variable_get('@event_counts')).to eq({})
148
+ end
131
149
  end
@@ -0,0 +1,9 @@
1
+ require 'volt/models'
2
+
3
+ describe Model do
4
+ it "should create a buffer from an ArrayModel" do
5
+ page = ReactiveValue.new(Model.new)
6
+
7
+ page._items = []
8
+ end
9
+ end
@@ -80,15 +80,20 @@ describe Model do
80
80
  it "should not call changed on other attributes" do
81
81
  a = ReactiveValue.new(Model.new)
82
82
 
83
- count = 0
84
- a._blue.on('changed') { count += 1 }
85
- expect(count).to eq(0)
83
+ blue_count = 0
84
+ green_count = 0
85
+ a._blue.on('changed') { blue_count += 1 }
86
+ a._green.on('changed') { green_count += 1}
87
+ expect(blue_count).to eq(0)
88
+ expect(green_count).to eq(0)
86
89
 
87
90
  a._green = 'one'
88
- expect(count).to eq(0)
91
+ expect(blue_count).to eq(0)
92
+ expect(green_count).to eq(1)
89
93
 
90
94
  a._blue = 'two'
91
- expect(count).to eq(1)
95
+ expect(blue_count).to eq(1)
96
+ expect(green_count).to eq(1)
92
97
 
93
98
  end
94
99
 
@@ -110,6 +115,12 @@ describe Model do
110
115
  end
111
116
 
112
117
  it "should store reactive values in arrays and trigger updates when those values change" do
118
+ a = ReactiveValue.new(Model.new)
119
+ b = ReactiveValue.new('blue')
120
+ a._items << b
121
+ b.cur = 'two'
122
+
123
+
113
124
  a = ReactiveValue.new(Model.new)
114
125
  b = ReactiveValue.new('blue')
115
126
  a._one = 1
@@ -241,10 +252,12 @@ describe Model do
241
252
  expect(count).to eq(0)
242
253
 
243
254
  a._blue._green = 5
244
- expect(count).to eq(1)
245
255
 
246
- a._blue = 22
256
+ # TODO: Should actually just equal one
247
257
  expect(count).to eq(2)
258
+
259
+ a._blue = 22
260
+ expect(count).to eq(3)
248
261
  end
249
262
 
250
263
  it "should trigger changed when a value is deleted" do
@@ -407,11 +420,11 @@ describe Model do
407
420
  c.on('changed') { count2 += 1 }
408
421
  expect(count1).to eq(0)
409
422
 
410
- b._complete = true
423
+ a._complete = true
411
424
  expect(count1).to eq(1)
412
425
  expect(count2).to eq(1)
413
426
 
414
- b._complete = false
427
+ a._complete = false
415
428
  expect(count1).to eq(2)
416
429
  expect(count2).to eq(2)
417
430
 
@@ -252,7 +252,6 @@ describe ReactiveArray do
252
252
  model._current.on('changed') { count += 1 }
253
253
  expect(count).to eq(0)
254
254
 
255
- ObjectTracker.process_queue
256
255
  model._items.delete_at(0)
257
256
 
258
257
  expect(count).to eq(1)
@@ -301,5 +300,31 @@ describe ReactiveArray do
301
300
 
302
301
 
303
302
  end
303
+
304
+ it "should trigger changed with a negative index assignment" do
305
+ a = ReactiveValue.new(ReactiveArray.new([1,2,3]))
306
+
307
+ count_0 = 0
308
+ count_1 = 0
309
+
310
+ a[0].on('changed') { count_0 += 1 }
311
+ a[1].on('changed') { count_1 += 1 }
312
+
313
+ a[-2] = 50
314
+
315
+ expect(count_0).to eq(0)
316
+ expect(count_1).to eq(1)
317
+ end
318
+
319
+ it "should not trigger on other indicies" do
320
+ a = ReactiveValue.new(ReactiveArray.new([1,2,3]))
321
+
322
+ count = 0
323
+ a[0].on('changed') { count += 1 }
324
+ expect(count).to eq(0)
325
+
326
+ a[1] = 5
327
+ expect(count).to eq(0)
328
+ end
304
329
  end
305
330
  end
@@ -0,0 +1,28 @@
1
+ # require 'volt/models'
2
+ #
3
+ # class Test1 < Model
4
+ # def test
5
+ # puts "YEP"
6
+ #
7
+ # {:yes => true}
8
+ # end
9
+ # end
10
+ #
11
+ # describe ReactiveValue do
12
+ # it "should not trigger a model method multiple times" do
13
+ # a = ReactiveValue.new(Test1.new)
14
+ #
15
+ # test = a.test
16
+ #
17
+ # test.on('changed') { puts "CH" }
18
+ # a._name.on('changed') { puts "NAME CH" }
19
+ #
20
+ # puts "--------"
21
+ # a._name = 'ok'
22
+ #
23
+ #
24
+ #
25
+ #
26
+ # # a.test
27
+ # end
28
+ # end
@@ -350,4 +350,23 @@ describe ReactiveValue do
350
350
 
351
351
  expect(a.deep_cur).to eq({_names: ['bob', 'jim']})
352
352
  end
353
+
354
+ it "should remove any event bindings bound through a reactive value when the value changes" do
355
+ a = ReactiveValue.new(Model.new)
356
+
357
+ a._info = {_name: 'Test'}
358
+ info = a._info.cur
359
+
360
+ expect(info.listeners.size).to eq(0)
361
+
362
+ listener = a._info.on('changed') { }
363
+
364
+ expect(info.listeners.size).to eq(1)
365
+
366
+ # Listener should be moved to the new object
367
+ a._info = {}
368
+
369
+ expect(info.listeners.size).to eq(0)
370
+ end
371
+
353
372
  end
@@ -0,0 +1,39 @@
1
+ require 'volt/models'
2
+
3
+ class TestModel < Model
4
+ validate :_name, length: 4
5
+ end
6
+
7
+ describe Model do
8
+ it "should validate the name" do
9
+ expect(TestModel.new.errors).to eq({:_name => ["must be at least 4 chars"]})
10
+ end
11
+
12
+ it "should show marked validations once they are marked" do
13
+ model = TestModel.new
14
+
15
+ expect(model.marked_errors).to eq({})
16
+
17
+ model.mark_field!(:_name)
18
+
19
+ expect(model.marked_errors).to eq(
20
+ {
21
+ :_name => ["must be at least 4 chars"]
22
+ }
23
+ )
24
+ end
25
+
26
+ it "should show all fields in marked errors once saved" do
27
+ model = TestModel.new
28
+
29
+ expect(model.marked_errors).to eq({})
30
+
31
+ model.save!
32
+
33
+ expect(model.marked_errors).to eq(
34
+ {
35
+ :_name => ["must be at least 4 chars"]
36
+ }
37
+ )
38
+ end
39
+ end
@@ -1,6 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'volt/page/bindings/content_binding'
3
3
  require 'volt/page/targets/attribute_target'
4
+ require 'volt/page/targets/dom_section'
4
5
  require 'volt/page/template_renderer'
5
6
 
6
7
 
@@ -1,3 +1,4 @@
1
+ require 'spec_helper'
1
2
  require 'volt/page/bindings/template_binding'
2
3
 
3
4
  # Setup page stub
@@ -26,6 +27,59 @@ describe TemplateBinding do
26
27
  $page = nil
27
28
  end
28
29
 
30
+ it "should lookup nested controller action" do
31
+ @templates = {
32
+ 'home/index/blog/nav' => '',
33
+ 'home/comments/new/body' => '',
34
+ }
35
+
36
+ result = @template_binding.path_for_template('comments/new').last
37
+ expect(result).to eq(['home', 'comments_controller', 'new'])
38
+ end
39
+
40
+ it "it should not look in the local component/controller for a specified controller/action" do
41
+ @templates = {
42
+ 'home/comments/new/body' => ''
43
+ }
44
+
45
+ path, result = @template_binding.path_for_template('comments/new')
46
+ expect(path).to eq('home/comments/new/body')
47
+ expect(result).to eq(['home', 'comments_controller', 'new'])
48
+ end
49
+
50
+
51
+ it "should handle a tripple lookup" do
52
+ @templates = {
53
+ 'home/comments/new/errors' => '',
54
+ 'comments/new/errors/body' => ''
55
+ }
56
+
57
+ path, result = @template_binding.path_for_template('comments/new/errors')
58
+ expect(path).to eq('home/comments/new/errors')
59
+ expect(result).to eq(nil)
60
+ end
61
+
62
+ it "should handle a tripple lookup to controllers" do
63
+ @templates = {
64
+ 'comments/new/errors/body' => ''
65
+ }
66
+
67
+ path, result = @template_binding.path_for_template('comments/new/errors')
68
+ expect(path).to eq('comments/new/errors/body')
69
+ expect(result).to eq(['comments', 'new_controller', 'errors'])
70
+ end
71
+
72
+
73
+ it "should find a matching component" do
74
+ @templates = {
75
+ 'comments/new/index/body' => ''
76
+ }
77
+
78
+ path, result = @template_binding.path_for_template('comments/new')
79
+ expect(path).to eq('comments/new/index/body')
80
+ expect(result).to eq(['comments', 'new_controller', 'index'])
81
+ end
82
+
29
83
  it "should lookup sub-templates within its own file" do
30
84
  @templates = {
31
85
  'home/index/blog/nav' => '',