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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/app/volt/tasks/query_tasks.rb +0 -7
- data/app/volt/tasks/store_tasks.rb +0 -6
- data/docs/UPGRADE_GUIDE.md +2 -0
- data/lib/volt/cli/asset_compile.rb +2 -2
- data/lib/volt/cli/console.rb +21 -0
- data/lib/volt/config.rb +0 -10
- data/lib/volt/controllers/collection_helpers.rb +18 -0
- data/lib/volt/controllers/model_controller.rb +2 -12
- data/lib/volt/extra_core/object.rb +19 -0
- data/lib/volt/models.rb +14 -9
- data/lib/volt/models/array_model.rb +62 -22
- data/lib/volt/models/associations.rb +16 -1
- data/lib/volt/models/model.rb +27 -15
- data/lib/volt/models/model_helpers/model_helpers.rb +29 -0
- data/lib/volt/models/permissions.rb +15 -4
- data/lib/volt/models/persistors/array_store.rb +40 -0
- data/lib/volt/models/persistors/model_store.rb +2 -2
- data/lib/volt/models/persistors/query/query_listener.rb +3 -1
- data/lib/volt/models/persistors/store.rb +2 -1
- data/lib/volt/models/root_models/root_models.rb +31 -0
- data/lib/volt/models/root_models/store_root.rb +36 -0
- data/lib/volt/models/validators/unique_validator.rb +1 -1
- data/lib/volt/page/bindings/each_binding.rb +56 -47
- data/lib/volt/page/page.rb +5 -5
- data/lib/volt/reactive/reactive_array.rb +9 -6
- data/lib/volt/server.rb +2 -2
- data/lib/volt/server/component_templates.rb +7 -4
- data/lib/volt/server/message_bus/message_encoder.rb +9 -1
- data/lib/volt/server/rack/component_code.rb +8 -1
- data/lib/volt/server/rack/index_files.rb +5 -2
- data/lib/volt/tasks/{task_handler.rb → task.rb} +6 -6
- data/lib/volt/utils/promise.rb +429 -0
- data/lib/volt/utils/promise_extensions.rb +79 -0
- data/lib/volt/version.rb +1 -1
- data/lib/volt/volt/app.rb +5 -2
- data/lib/volt/volt/server_setup/app.rb +28 -7
- data/spec/apps/kitchen_sink/app/main/controllers/server/simple_http_controller.rb +1 -1
- data/spec/apps/kitchen_sink/app/main/views/main/store.html +3 -0
- data/spec/extra_core/object_spec.rb +13 -0
- data/spec/integration/store_spec.rb +10 -0
- data/spec/models/associations_spec.rb +48 -26
- data/spec/models/model_spec.rb +23 -7
- data/spec/models/persistors/store_spec.rb +28 -0
- data/spec/models/validators/unique_validator_spec.rb +1 -1
- data/spec/spec_helper.rb +4 -1
- data/spec/utils/promise_extensions_spec.rb +42 -0
- data/templates/component/config/initializers/boot.rb +10 -0
- data/templates/{project/app → component/config/initializers/client}/.empty_directory +0 -0
- data/templates/component/config/initializers/server/.empty_directory +0 -0
- data/templates/newgem/app/newgem/config/initializers/client/.empty_directory +0 -0
- data/templates/newgem/app/newgem/config/initializers/server/.empty_directory +0 -0
- data/templates/project/Gemfile.tt +6 -2
- data/templates/project/app/main/config/initializers/boot.rb +10 -0
- data/templates/project/app/main/config/initializers/client/.empty_directory +0 -0
- data/templates/project/app/main/config/initializers/server/.empty_directory +0 -0
- data/templates/project/config/app.rb.tt +3 -0
- data/templates/project/config/initializers/client/.empty_directory +0 -0
- data/templates/project/config/initializers/server/.empty_directory +0 -0
- metadata +22 -5
- 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
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/
|
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
|
-
|
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
|
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
|
-
|
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
|
@@ -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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
21
|
-
|
29
|
+
expect(address.person.sync.id).to eq(@person.id)
|
30
|
+
end
|
22
31
|
|
23
|
-
|
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
|
27
|
-
|
50
|
+
it 'should support has_one' do
|
51
|
+
address = store.addresses.create({street: '223344 Something St'}).sync
|
28
52
|
|
29
|
-
|
30
|
-
|
31
|
-
|
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 '
|
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
|
-
|
38
|
-
end.to raise_error("
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
data/spec/models/model_spec.rb
CHANGED
@@ -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.
|
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.
|
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
|
562
|
-
b = store._items
|
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 = '
|
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
|