volt 0.9.3.pre2 → 0.9.3.pre3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/app/volt/tasks/query_tasks.rb +0 -7
  4. data/app/volt/tasks/store_tasks.rb +0 -6
  5. data/docs/UPGRADE_GUIDE.md +2 -0
  6. data/lib/volt/cli/asset_compile.rb +2 -2
  7. data/lib/volt/cli/console.rb +21 -0
  8. data/lib/volt/config.rb +0 -10
  9. data/lib/volt/controllers/collection_helpers.rb +18 -0
  10. data/lib/volt/controllers/model_controller.rb +2 -12
  11. data/lib/volt/extra_core/object.rb +19 -0
  12. data/lib/volt/models.rb +14 -9
  13. data/lib/volt/models/array_model.rb +62 -22
  14. data/lib/volt/models/associations.rb +16 -1
  15. data/lib/volt/models/model.rb +27 -15
  16. data/lib/volt/models/model_helpers/model_helpers.rb +29 -0
  17. data/lib/volt/models/permissions.rb +15 -4
  18. data/lib/volt/models/persistors/array_store.rb +40 -0
  19. data/lib/volt/models/persistors/model_store.rb +2 -2
  20. data/lib/volt/models/persistors/query/query_listener.rb +3 -1
  21. data/lib/volt/models/persistors/store.rb +2 -1
  22. data/lib/volt/models/root_models/root_models.rb +31 -0
  23. data/lib/volt/models/root_models/store_root.rb +36 -0
  24. data/lib/volt/models/validators/unique_validator.rb +1 -1
  25. data/lib/volt/page/bindings/each_binding.rb +56 -47
  26. data/lib/volt/page/page.rb +5 -5
  27. data/lib/volt/reactive/reactive_array.rb +9 -6
  28. data/lib/volt/server.rb +2 -2
  29. data/lib/volt/server/component_templates.rb +7 -4
  30. data/lib/volt/server/message_bus/message_encoder.rb +9 -1
  31. data/lib/volt/server/rack/component_code.rb +8 -1
  32. data/lib/volt/server/rack/index_files.rb +5 -2
  33. data/lib/volt/tasks/{task_handler.rb → task.rb} +6 -6
  34. data/lib/volt/utils/promise.rb +429 -0
  35. data/lib/volt/utils/promise_extensions.rb +79 -0
  36. data/lib/volt/version.rb +1 -1
  37. data/lib/volt/volt/app.rb +5 -2
  38. data/lib/volt/volt/server_setup/app.rb +28 -7
  39. data/spec/apps/kitchen_sink/app/main/controllers/server/simple_http_controller.rb +1 -1
  40. data/spec/apps/kitchen_sink/app/main/views/main/store.html +3 -0
  41. data/spec/extra_core/object_spec.rb +13 -0
  42. data/spec/integration/store_spec.rb +10 -0
  43. data/spec/models/associations_spec.rb +48 -26
  44. data/spec/models/model_spec.rb +23 -7
  45. data/spec/models/persistors/store_spec.rb +28 -0
  46. data/spec/models/validators/unique_validator_spec.rb +1 -1
  47. data/spec/spec_helper.rb +4 -1
  48. data/spec/utils/promise_extensions_spec.rb +42 -0
  49. data/templates/component/config/initializers/boot.rb +10 -0
  50. data/templates/{project/app → component/config/initializers/client}/.empty_directory +0 -0
  51. data/templates/component/config/initializers/server/.empty_directory +0 -0
  52. data/templates/newgem/app/newgem/config/initializers/client/.empty_directory +0 -0
  53. data/templates/newgem/app/newgem/config/initializers/server/.empty_directory +0 -0
  54. data/templates/project/Gemfile.tt +6 -2
  55. data/templates/project/app/main/config/initializers/boot.rb +10 -0
  56. data/templates/project/app/main/config/initializers/client/.empty_directory +0 -0
  57. data/templates/project/app/main/config/initializers/server/.empty_directory +0 -0
  58. data/templates/project/config/app.rb.tt +3 -0
  59. data/templates/project/config/initializers/client/.empty_directory +0 -0
  60. data/templates/project/config/initializers/server/.empty_directory +0 -0
  61. metadata +22 -5
  62. data/lib/volt/utils/promise_patch.rb +0 -70
@@ -0,0 +1,79 @@
1
+ # Require the original promise library first.
2
+ require 'volt/utils/promise'
3
+
4
+ # A temp patch for promises until https://github.com/opal/opal/pull/725 is released.
5
+ class Promise
6
+
7
+ def method_missing(method_name, *args, &block)
8
+ promise = self.then do |value|
9
+ value.send(method_name, *args, &block)
10
+ end
11
+
12
+ promise
13
+ end
14
+
15
+ # Allow .each to be called directly on promises
16
+ def each(&block)
17
+ raise ArgumentError, 'no block given' unless block
18
+
19
+ self.then do |val|
20
+ val.each(&block)
21
+ end
22
+ end
23
+
24
+ # Improve #inspect to not show nested promises.
25
+ def inspect
26
+ result = "#<#{self.class}(#{object_id})"
27
+
28
+ if @next
29
+ result += " >> #{@next.inspect}"
30
+ end
31
+
32
+ if realized?
33
+ value = value_or_error
34
+
35
+ loop do
36
+ if value.is_a?(Promise) && value.realized?
37
+ value = value.value_or_error
38
+ else
39
+ break
40
+ end
41
+ end
42
+
43
+ result += ": #{value.inspect}>"
44
+ else
45
+ result += ">"
46
+ end
47
+
48
+ result
49
+ end
50
+
51
+ def value_or_error
52
+ @value || @error
53
+ end
54
+
55
+
56
+ # Waits for the promise to resolve (assuming it is blocking on
57
+ # the server) and returns the result.
58
+ def sync
59
+ raise ".sync can only be used on the client" if Volt.client?
60
+
61
+ result = nil
62
+ error = nil
63
+
64
+ self.then do |val|
65
+ result = val
66
+ end.fail do |err|
67
+ error = err
68
+ end
69
+
70
+ if error
71
+ err_str = "Exception in Promise at .sync: #{error.inspect}"
72
+ err_str += error.backtrace.join("\n")
73
+ Volt.logger.error(err_str)
74
+ fail error
75
+ else
76
+ return result
77
+ end
78
+ end
79
+ end
data/lib/volt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Volt
2
2
  module Version
3
- STRING = '0.9.3.pre2'
3
+ STRING = '0.9.3.pre3'
4
4
  end
5
5
  end
data/lib/volt/volt/app.rb CHANGED
@@ -4,7 +4,7 @@ require 'opal'
4
4
  require 'volt'
5
5
  require 'volt/models'
6
6
  require 'volt/controllers/model_controller'
7
- require 'volt/tasks/task_handler'
7
+ require 'volt/tasks/task'
8
8
  require 'volt/page/bindings/bindings'
9
9
  require 'volt/page/template_renderer'
10
10
  require 'volt/page/string_template_renderer'
@@ -53,8 +53,11 @@ module Volt
53
53
  setup_page
54
54
 
55
55
  if RUBY_PLATFORM != 'opal'
56
+ # Setup all app paths
57
+ setup_paths
58
+
56
59
  # Require in app and initializers
57
- Volt.run_app_and_initializers unless RUBY_PLATFORM == 'opal'
60
+ run_app_and_initializers unless RUBY_PLATFORM == 'opal'
58
61
 
59
62
  # abort_on_exception is a useful debugging tool, and in my opinion something
60
63
  # you probbaly want on. That said you can disable it if you need.
@@ -6,22 +6,19 @@ end
6
6
  module Volt
7
7
  module ServerSetup
8
8
  module App
9
- def load_app_code
9
+ def setup_paths
10
10
  # Load component paths
11
11
  @component_paths = ComponentPaths.new(@app_path)
12
12
  @component_paths.require_in_components(@page || $page)
13
+ end
13
14
 
15
+ def load_app_code
14
16
  setup_router
15
17
  require_http_controllers
16
18
  end
17
19
 
18
20
  def setup_router
19
- # Find the route file
20
- home_path = @component_paths.component_paths('main').first
21
- routes = File.read("#{home_path}/config/routes.rb")
22
- @router = Routes.new.define do
23
- eval(routes)
24
- end
21
+ @router = Routes.new
25
22
  end
26
23
 
27
24
  def require_http_controllers
@@ -35,6 +32,28 @@ module Volt
35
32
  end
36
33
  end
37
34
 
35
+
36
+ # Load in all .rb files in the initializers folders and the config/app.rb
37
+ # file.
38
+ def run_app_and_initializers
39
+ files = ["#{Volt.root}/config/app.rb"]
40
+
41
+ # Include the root initializers
42
+ files += Dir[Volt.root + '/config/initializers/*.rb']
43
+ files += Dir[Volt.root + '/config/initializers/server/*.rb']
44
+
45
+ # Get initializers for each component
46
+ component_paths.app_folders do |app_folder|
47
+ files += Dir["#{app_folder}/*/config/initializers/*.rb"]
48
+ files += Dir["#{app_folder}/*/config/initializers/server/*.rb"]
49
+ end
50
+
51
+ files.each do |initializer|
52
+ require(initializer)
53
+ end
54
+ end
55
+
56
+
38
57
  def reset_query_pool!
39
58
  if RUBY_PLATFORM != 'opal'
40
59
  # The load path isn't setup at the top of app.rb, so we wait to require
@@ -48,6 +67,8 @@ module Volt
48
67
  end
49
68
 
50
69
  def start_message_bus
70
+ return if ENV['NO_MESSAGE_BUS']
71
+
51
72
  unless RUBY_PLATFORM == 'opal'
52
73
 
53
74
  # Don't run in test env, since you probably only have one set of tests
@@ -5,7 +5,7 @@ module Main
5
5
  end
6
6
 
7
7
  def show
8
- render text: "You had me at #{store._simple_http_tests.first._name}"
8
+ render text: "You had me at #{store._simple_http_tests.first._name.sync}"
9
9
  end
10
10
 
11
11
  def upload
@@ -4,3 +4,6 @@
4
4
  <:Body>
5
5
  <h1>Store</h1>
6
6
 
7
+ <p>Sync between nested root properties.</p>
8
+ <input id="field1" value="{{ store._root_prop!._name }}" /><br />
9
+ <input id="field2" value="{{ store._root_prop!._name }}" />
@@ -11,4 +11,17 @@ describe Object do
11
11
  expect(Object.new.present?).to eq(true)
12
12
  expect(nil.present?).to eq(false)
13
13
  end
14
+
15
+ it 'should allow you to call .then to get a Promise with the object resolved' do
16
+ promise = 5.then
17
+
18
+ expect(promise.resolved?).to eq(true)
19
+ expect(promise.value).to eq(5)
20
+ end
21
+
22
+ it 'should allow you to call .then with a block that will yield the promise' do
23
+ 5.then do |val|
24
+ expect(val).to eq(5)
25
+ end
26
+ end
14
27
  end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'store', type: :feature, sauce: true do
4
+ it 'should sync between nested root properties on store' do
5
+ visit '/store'
6
+
7
+ fill_in('field1', with: 'should sync')
8
+ expect(find('#field2').value).to eq('should sync')
9
+ end
10
+ end
@@ -6,45 +6,67 @@ end
6
6
 
7
7
  class ::Address < Volt::Model
8
8
  belongs_to :person
9
+ has_one :zip_info
10
+ end
11
+
12
+ class ::ZipInfo < Volt::Model
13
+ belongs_to :address
9
14
  end
10
15
 
11
16
  describe Volt::Associations do
12
17
  if RUBY_PLATFORM != 'opal'
13
- before do
14
- store._people! << { name: 'Jimmy' }
15
- @person = store._people[0]
16
- @person._addresses! << { city: 'Bozeman' }
17
- @person._addresses << { city: 'Portland' }
18
- end
18
+ describe "with samples" do
19
+ before do
20
+ store._people! << { name: 'Jimmy' }
21
+ @person = store._people[0].sync
22
+ @person._addresses! << { city: 'Bozeman' }
23
+ @person._addresses << { city: 'Portland' }
24
+ end
25
+
26
+ it 'should associate via belongs_to' do
27
+ address = store._addresses!.fetch_first.sync
19
28
 
20
- it 'should associate via belongs_to' do
21
- address = store._addresses!.fetch_first.sync
29
+ expect(address.person.sync.id).to eq(@person.id)
30
+ end
22
31
 
23
- expect(address.person.sync.id).to eq(@person.id)
32
+ it 'should associate via has_many' do
33
+ store._people!.first do |person|
34
+
35
+ addresses = person.addresses.all
36
+ expect(addresses.size.sync).to eq(2)
37
+ expect(addresses[0]._city.sync).to eq('Bozeman')
38
+ end
39
+ end
40
+
41
+ it 'warns users if persistor is not a ModelStore' do
42
+ store = Volt::Model.new({}, persistor: Volt::Persistors::Flash)
43
+ expect do
44
+ store.send(:association_with_root_model, :blah)
45
+ end.to raise_error("blah currently only works on the store and page collection "\
46
+ "(support for other collections coming soon)")
47
+ end
24
48
  end
25
49
 
26
- it 'should associate via has_many' do
27
- person = store._people!.fetch_first.sync
50
+ it 'should support has_one' do
51
+ address = store.addresses.create({street: '223344 Something St'}).sync
28
52
 
29
- addresses = person.addresses.fetch.sync
30
- expect(addresses.size).to eq(2)
31
- expect(addresses[0]._city).to eq('Bozeman')
53
+ zip_info = store.zip_infos.create({zip: '29344', address_id: address.id}).sync
54
+
55
+ address2 = store.addresses.first.sync
56
+ expect(address2.zip_info.sync.id).to eq(zip_info.id)
32
57
  end
33
58
 
34
- it 'warns users if persistor is not a ModelStore' do
35
- store = Volt::Model.new({}, persistor: Volt::Persistors::Flash)
59
+ it 'should raise an exception when setting up a plural has one' do
36
60
  expect do
37
- store.send(:association_with_root_model, :blah)
38
- end.to raise_error("blah currently only works on the store and page collection "\
39
- "(support for other collections coming soon)")
61
+ Address.send(:has_one, :isotopes)
62
+ end.to raise_error(NameError, "has_one takes a singluar association name")
40
63
  end
41
64
 
42
- # it 'should assign the reference_id for has_many' do
43
- # bob = Person.new
44
- # bob.addresses << {:street => '1234 awesome street'}
45
- # puts "Bob: #{bob.inspect} - #{bob.addresses.size}"
46
- # expect(bob.addresses[0].person_id).to eq(bob.id)
47
- # expect(bob.id).to_not eq(nil)
48
- # end
65
+ it 'should assign the reference_id for has_many' do
66
+ bob = store.people.create.sync
67
+ bob.addresses.create({:street => '1234 awesome street'})
68
+ expect(bob.addresses[0].sync.person_id).to eq(bob.id)
69
+ expect(bob.id).to_not eq(nil)
70
+ end
49
71
  end
50
72
  end
@@ -436,6 +436,22 @@ describe Volt::Model do
436
436
  expect(items).to eq(a)
437
437
  end
438
438
 
439
+ describe "first or create" do
440
+ it 'should create an item if one does not exist in the collection' do
441
+ page = Volt::Model.new
442
+
443
+ result = page._items.first_or_create
444
+ expect(result.class).to eq(Promise)
445
+
446
+ result.then do |item|
447
+ expect(page._items.size).to eq(1)
448
+ page._items[0].then do |item2|
449
+ expect(item).to eq(item2)
450
+ end
451
+ end
452
+ end
453
+ end
454
+
439
455
  describe 'model paths' do
440
456
  before do
441
457
  @model = Volt::Model.new
@@ -542,14 +558,14 @@ describe Volt::Model do
542
558
  count = 0
543
559
 
544
560
  # count the number of todos
545
- query2.fetch { |v| count += v.size }
561
+ query2.all.each { |v| count += 1 }
546
562
 
547
563
  expect(count).to eq(0)
548
564
 
549
565
  query1 << { label: 'One' }
550
566
 
551
567
  count = 0
552
- query2.fetch { |v| count += v.size }
568
+ query2.all.each { |v| count += 1 }
553
569
 
554
570
  expect(count).to eq(1)
555
571
  end
@@ -558,13 +574,13 @@ describe Volt::Model do
558
574
  store._items << { name: 'One' }
559
575
  store._items << { name: 'Two' }
560
576
 
561
- a = store._items.fetch.sync
562
- b = store._items.fetch.sync
577
+ a = store._items
578
+ b = store._items
563
579
 
564
- expect(a.size).to eq(2)
565
- expect(b.size).to eq(2)
580
+ expect(a.size.sync).to eq(2)
581
+ expect(b.size.sync).to eq(2)
566
582
 
567
- expect(a.to_a).to eq(b.to_a)
583
+ expect(a.all.to_a.sync).to eq(b.all.to_a.sync)
568
584
  end
569
585
  end
570
586
 
@@ -35,4 +35,32 @@ describe Volt::Persistors::Store do
35
35
  model._abc = 456
36
36
  expect(persistor.removed(:abc)).to eq(persistor.changed(:abc))
37
37
  end
38
+
39
+ unless RUBY_PLATFORM == 'opal'
40
+ before do
41
+ model = store._nesters.create(name: 'One').sync
42
+ model._subone = {name: 'Two'}
43
+ end
44
+
45
+ it 'should reload a model with nested hash models' do
46
+ model2 = store._nesters.first.sync
47
+
48
+ expect(model2.to_h.without(:id, :subone)).to eq(name: 'One')
49
+ expect(model2._subone.to_h.without(:id)).to eq(name: 'Two')
50
+ end
51
+
52
+ it 'should reload nested hash models with the same parent persistor' do
53
+ model = store._nesters.first.sync
54
+ expect(model.persistor).to eq(model._subone.persistor)
55
+ end
56
+ end
57
+
58
+ unless RUBY_PLATFORM == 'opal'
59
+ it 'should sync properties on the root' do
60
+ store._name = 'Jim'
61
+ store._name.then do |name|
62
+ expect(name).to eq('Jim')
63
+ end
64
+ end
65
+ end
38
66
  end
@@ -19,7 +19,7 @@ unless RUBY_PLATFORM == 'opal'
19
19
  it 'should not increase count of the total records in the store' do
20
20
  store._fridges << { name: 'swift' }
21
21
  store._fridges << { name: 'swift' }
22
- expect(store._fridges.count).to eq(1)
22
+ expect(store._fridges.count.sync).to eq(1)
23
23
  end
24
24
  end
25
25
  end
data/spec/spec_helper.rb CHANGED
@@ -25,6 +25,9 @@ unless RUBY_PLATFORM == 'opal'
25
25
  add_filter 'lib/volt/utils/local_storage'
26
26
  add_filter 'lib/volt/benchmark'
27
27
 
28
+ # Copied in from opal until 0.8 comes out.
29
+ add_filter 'lib/volt/utils/promise'
30
+
28
31
  # Copied in from concurrent-ruby, waiting for gem release
29
32
  add_filter 'lib/volt/utils/read_write_lock.rb'
30
33
  end
@@ -44,7 +47,7 @@ unless RUBY_PLATFORM == 'opal'
44
47
  # the seed, which is printed after each run.
45
48
  # --seed 1234
46
49
  config.order = 'random'
47
- config.seed = '45091'
50
+ # config.seed = '61236'
48
51
  end
49
52
 
50
53
  end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ def count_occurences(str, find)
5
+ count = 0
6
+
7
+ loop do
8
+ index = str.index(find)
9
+
10
+ break unless index
11
+ count += 1
12
+ str = str[index+1..-1]
13
+ end
14
+
15
+ count
16
+ end
17
+
18
+ describe Promise do
19
+ it 'should allow you to call methods that will be called on the resolved value and return a new promise' do
20
+ a = Promise.new.resolve(5)
21
+
22
+ float_promise = a.to_f
23
+ expect(float_promise.class).to eq(Promise)
24
+ float_promise.then do |val|
25
+ expect(val).to eq(5)
26
+ end
27
+ end
28
+
29
+ it 'should patch inspect on Promise so that nested Promise are shown as a single promise' do
30
+ a = Promise.new
31
+ b = Promise.new.then { a }
32
+ a.resolve(1)
33
+ b.resolve(2)
34
+
35
+ # There is currently an infinity loop in scan in opal.
36
+ # https://github.com/opal/opal/issues/457
37
+ # TODO: Remove when opal 0.8 comes out.
38
+ # expect(b.inspect.scan('Promise').size).to eq(1)
39
+
40
+ expect(count_occurences(b.inspect, 'Promise')).to eq(1)
41
+ end
42
+ end