volt 0.9.3.pre2 → 0.9.3.pre3

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 (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