volt 0.7.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
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' => '',