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