volt 0.9.6 → 0.9.7.pre2
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/.gitignore +2 -1
- data/CHANGELOG.md +26 -0
- data/Gemfile +6 -1
- data/README.md +2 -0
- data/Rakefile +1 -0
- data/app/volt/models/active_volt_instance.rb +8 -6
- data/app/volt/models/user.rb +11 -2
- data/app/volt/models/volt_app_property.rb +8 -0
- data/app/volt/tasks/query_tasks.rb +23 -31
- data/app/volt/tasks/store_tasks.rb +2 -2
- data/app/volt/tasks/volt_admin_tasks.rb +24 -0
- data/docs/UPGRADE_GUIDE.md +6 -0
- data/lib/volt.rb +19 -12
- data/lib/volt/boot.rb +1 -0
- data/lib/volt/cli.rb +19 -8
- data/lib/volt/cli/console.rb +0 -1
- data/lib/volt/cli/generators.rb +14 -3
- data/lib/volt/cli/migrate.rb +26 -0
- data/lib/volt/config.rb +17 -4
- data/lib/volt/controllers/http_controller.rb +12 -0
- data/lib/volt/data_stores/base_adaptor_client.rb +2 -2
- data/lib/volt/data_stores/base_adaptor_server.rb +2 -0
- data/lib/volt/data_stores/data_store.rb +20 -14
- data/lib/volt/extra_core/class.rb +28 -14
- data/lib/volt/extra_core/hash.rb +5 -0
- data/lib/volt/extra_core/string.rb +3 -1
- data/lib/volt/helpers/time.rb +9 -43
- data/lib/volt/helpers/time/calculations.rb +204 -0
- data/lib/volt/helpers/time/distance.rb +63 -0
- data/lib/volt/helpers/time/duration.rb +71 -0
- data/lib/volt/helpers/time/local_calculations.rb +49 -0
- data/lib/volt/helpers/time/local_volt_time.rb +23 -0
- data/lib/volt/helpers/time/numeric.rb +59 -0
- data/lib/volt/helpers/time/volt_time.rb +170 -0
- data/lib/volt/models.rb +5 -0
- data/lib/volt/models/array_model.rb +33 -6
- data/lib/volt/models/associations.rb +146 -23
- data/lib/volt/models/buffer.rb +38 -41
- data/lib/volt/models/cursor.rb +15 -0
- data/lib/volt/models/errors.rb +11 -0
- data/lib/volt/models/field_helpers.rb +108 -68
- data/lib/volt/models/helpers/array_model.rb +4 -0
- data/lib/volt/models/helpers/base.rb +8 -1
- data/lib/volt/models/helpers/change_helpers.rb +31 -12
- data/lib/volt/models/helpers/defaults.rb +15 -0
- data/lib/volt/models/location.rb +20 -6
- data/lib/volt/models/migrations/migration.rb +23 -0
- data/lib/volt/models/migrations/migration_runner.rb +146 -0
- data/lib/volt/models/model.rb +38 -1
- data/lib/volt/models/permissions.rb +8 -1
- data/lib/volt/models/persistors/array_store.rb +87 -8
- data/lib/volt/models/persistors/base.rb +19 -0
- data/lib/volt/models/persistors/model_store.rb +1 -1
- data/lib/volt/models/persistors/page.rb +4 -1
- data/lib/volt/models/persistors/query/query_identifier.rb +102 -0
- data/lib/volt/models/persistors/query/query_listener.rb +57 -12
- data/lib/volt/models/root_models/root_models.rb +19 -0
- data/lib/volt/models/url.rb +11 -2
- data/lib/volt/models/validations/validations.rb +5 -2
- data/lib/volt/models/validators/type_validator.rb +11 -0
- data/lib/volt/models/validators/unique_validator.rb +2 -2
- data/lib/volt/page/bindings/attribute_binding.rb +23 -1
- data/lib/volt/page/targets/attribute_section.rb +7 -0
- data/lib/volt/page/targets/binding_document/component_node.rb +44 -18
- data/lib/volt/page/targets/binding_document/tag_node.rb +41 -0
- data/lib/volt/page/tasks.rb +16 -8
- data/lib/volt/queries/live_query.rb +109 -0
- data/lib/volt/queries/live_query_pool.rb +58 -0
- data/lib/volt/queries/live_subquery.rb +0 -0
- data/lib/volt/queries/query_association_splitter.rb +31 -0
- data/lib/volt/queries/query_diff.rb +100 -0
- data/lib/volt/queries/query_runner.rb +110 -0
- data/lib/volt/queries/query_subscription.rb +80 -0
- data/lib/volt/queries/query_subscription_pool.rb +37 -0
- data/lib/volt/reactive/eventable.rb +8 -0
- data/lib/volt/reactive/reactive_array.rb +0 -4
- data/lib/volt/router/routes.rb +81 -31
- data/lib/volt/server/message_bus/base_message_bus.rb +9 -3
- data/lib/volt/server/message_bus/peer_to_peer.rb +6 -6
- data/lib/volt/server/message_bus/peer_to_peer/server_tracker.rb +1 -1
- data/lib/volt/server/middleware/default_middleware_stack.rb +12 -8
- data/lib/volt/server/rack/component_paths.rb +31 -4
- data/lib/volt/server/rack/http_content_types.rb +62 -0
- data/lib/volt/server/rack/http_resource.rb +1 -1
- data/lib/volt/server/rack/index_files.rb +8 -1
- data/lib/volt/server/rack/opal_files.rb +16 -1
- data/lib/volt/server/rack/sprockets_helpers_setup.rb +32 -1
- data/lib/volt/server/socket_connection_handler.rb +16 -7
- data/lib/volt/server/template_handlers/sprockets_component_handler.rb +5 -3
- data/lib/volt/spec/capybara.rb +4 -3
- data/lib/volt/spec/setup.rb +5 -0
- data/lib/volt/tasks/dispatcher.rb +3 -1
- data/lib/volt/utils/data_transformer.rb +4 -4
- data/lib/volt/utils/ejson.rb +19 -6
- data/lib/volt/utils/promise_extensions.rb +1 -1
- data/lib/volt/utils/time_opal_patch.rb +749 -0
- data/lib/volt/utils/time_patch.rb +11 -4
- data/lib/volt/version.rb +1 -1
- data/lib/volt/volt/app.rb +19 -11
- data/lib/volt/volt/properties.rb +24 -0
- data/lib/volt/volt/server_setup/app.rb +30 -7
- data/lib/volt/volt/users.rb +15 -3
- data/spec/apps/kitchen_sink/Gemfile +5 -1
- data/spec/apps/kitchen_sink/app/main/config/routes.rb +1 -0
- data/spec/apps/kitchen_sink/app/main/controllers/save_controller.rb +1 -1
- data/spec/apps/kitchen_sink/app/main/controllers/server/simple_http_controller.rb +4 -0
- data/spec/apps/kitchen_sink/app/main/controllers/todos_controller.rb +4 -2
- data/spec/apps/kitchen_sink/app/main/models/post.rb +0 -1
- data/spec/apps/kitchen_sink/app/main/models/todo.rb +4 -0
- data/spec/apps/kitchen_sink/app/main/views/mailers/reset_password.html +10 -0
- data/spec/apps/kitchen_sink/app/main/views/todos/index.html +2 -0
- data/spec/apps/kitchen_sink/config/app.rb +2 -0
- data/spec/apps/migrations/config/db/migrations/1445111704_migration1.rb +7 -0
- data/spec/apps/migrations/config/db/migrations/1445113517_migration2.rb +7 -0
- data/spec/apps/migrations/config/db/migrations/1445115200_migration3.rb +7 -0
- data/spec/extra_core/class_spec.rb +10 -0
- data/spec/helpers/distance_spec.rb +35 -0
- data/spec/helpers/duration_spec.rb +160 -0
- data/spec/helpers/volt_time_spec.rb +275 -0
- data/spec/integration/callbacks_spec.rb +2 -1
- data/spec/integration/http_endpoints_spec.rb +4 -0
- data/spec/integration/save_spec.rb +1 -1
- data/spec/integration/todos_spec.rb +7 -5
- data/spec/models/array_model_spec.rb +17 -3
- data/spec/models/associations_spec.rb +48 -1
- data/spec/models/field_helpers_spec.rb +7 -3
- data/spec/models/migrations/migration_runner_spec.rb +69 -0
- data/spec/models/model_spec.rb +42 -8
- data/spec/models/permissions_spec.rb +20 -8
- data/spec/models/persistors/array_store_spec.rb +18 -0
- data/spec/models/persistors/page_spec.rb +15 -10
- data/spec/models/persistors/store_spec.rb +13 -3
- data/spec/models/url_spec.rb +4 -3
- data/spec/models/user_spec.rb +6 -3
- data/spec/models/user_validation_spec.rb +3 -3
- data/spec/models/validations_spec.rb +4 -0
- data/spec/models/validators/block_validations_spec.rb +9 -5
- data/spec/models/validators/email_validator_spec.rb +2 -0
- data/spec/models/validators/lifecycle_callbacks_spec.rb +86 -0
- data/spec/models/validators/unique_validator_spec.rb +1 -0
- data/spec/page/path_string_renderer_spec.rb +5 -0
- data/spec/queries/live_query_spec.rb +16 -0
- data/spec/queries/query_association_splitter_spec.rb +14 -0
- data/spec/queries/query_diff_spec.rb +132 -0
- data/spec/queries/query_identifier_spec.rb +98 -0
- data/spec/queries/query_runner_spec.rb +63 -0
- data/spec/queries/query_tracker_spec.rb +141 -0
- data/spec/router/routes_spec.rb +52 -21
- data/spec/server/middleware/rack_content_types_spec.rb +78 -0
- data/spec/server/rack/asset_files_spec.rb +38 -30
- data/spec/spec_helper.rb +8 -0
- data/spec/utils/ejson_spec.rb +9 -8
- data/spec/utils/ejson_volt_time_spec.rb +65 -0
- data/templates/migration/migration.rb.tt +9 -0
- data/templates/newgem/gitignore.tt +1 -0
- data/templates/project/Gemfile.tt +19 -2
- data/templates/project/README.md.tt +6 -1
- data/templates/project/app/main/config/dependencies.rb +6 -0
- data/templates/project/config/app.rb.tt +18 -4
- data/volt.gemspec +2 -2
- metadata +73 -16
- data/app/volt/tasks/live_query/live_query_pool.rb +0 -48
- data/app/volt/tasks/live_query/query_tracker.rb +0 -92
- data/spec/tasks/live_query_spec.rb +0 -18
- data/spec/tasks/query_tasks.rb +0 -7
- data/spec/tasks/query_tracker_spec.rb +0 -145
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'volt/reactive/eventable'
|
|
2
|
+
|
|
1
3
|
# When you get the root of a collection (.store, .page, etc...), it gives you
|
|
2
4
|
# back a unique class depending on the collection. This allows you to add
|
|
3
5
|
# things to the root easily.
|
|
@@ -8,6 +10,10 @@
|
|
|
8
10
|
|
|
9
11
|
# Create a model that is above all of the root models.
|
|
10
12
|
class BaseRootModel < Volt::Model
|
|
13
|
+
# return the name of the repo
|
|
14
|
+
def repo_name
|
|
15
|
+
self.class.name.gsub(/Root$/, '').underscore
|
|
16
|
+
end
|
|
11
17
|
end
|
|
12
18
|
|
|
13
19
|
|
|
@@ -19,6 +25,8 @@ end
|
|
|
19
25
|
|
|
20
26
|
module Volt
|
|
21
27
|
class RootModels
|
|
28
|
+
extend Eventable
|
|
29
|
+
|
|
22
30
|
class_attribute :model_classes
|
|
23
31
|
self.model_classes = []
|
|
24
32
|
|
|
@@ -31,6 +39,17 @@ module Volt
|
|
|
31
39
|
BaseRootModel.send(:define_method, method_name) do
|
|
32
40
|
get(method_name)
|
|
33
41
|
end
|
|
42
|
+
|
|
43
|
+
trigger!('model_created', klass)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Used mostly for testing, deletes a model
|
|
47
|
+
def self.remove_model_class(klass)
|
|
48
|
+
self.model_classes.reject! {|v| v == klass }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.clear_temporary
|
|
52
|
+
self.model_classes.reject! {|klass| klass.is_temporary }
|
|
34
53
|
end
|
|
35
54
|
end
|
|
36
55
|
end
|
data/lib/volt/models/url.rb
CHANGED
|
@@ -9,7 +9,7 @@ module Volt
|
|
|
9
9
|
|
|
10
10
|
# TODO: we need to make it so change events only trigger on changes
|
|
11
11
|
reactive_accessor :scheme, :host, :port, :path, :query, :fragment
|
|
12
|
-
attr_accessor :router
|
|
12
|
+
attr_accessor :router, :routes_loader
|
|
13
13
|
|
|
14
14
|
def initialize(router = nil)
|
|
15
15
|
@router = router
|
|
@@ -67,8 +67,17 @@ module Volt
|
|
|
67
67
|
# Full url rebuilds the url from it's constituent parts.
|
|
68
68
|
# The params passed in are used to generate the urls.
|
|
69
69
|
def url_for(params)
|
|
70
|
-
host_with_port = host
|
|
70
|
+
host_with_port = host || location.host
|
|
71
71
|
host_with_port += ":#{port}" if port && port != 80
|
|
72
|
+
scheme = scheme || location.scheme
|
|
73
|
+
|
|
74
|
+
unless RUBY_PLATFORM == 'opal'
|
|
75
|
+
# lazy load routes and views on the server
|
|
76
|
+
if !@router && @routes_loader
|
|
77
|
+
# Load the templates
|
|
78
|
+
@routes_loader.call
|
|
79
|
+
end
|
|
80
|
+
end
|
|
72
81
|
|
|
73
82
|
path, params = @router.params_to_url(params)
|
|
74
83
|
|
|
@@ -10,6 +10,7 @@ require 'volt/models/validators/unique_validator'
|
|
|
10
10
|
require 'volt/models/validators/type_validator'
|
|
11
11
|
|
|
12
12
|
module Volt
|
|
13
|
+
|
|
13
14
|
# Include in any class to get validation logic
|
|
14
15
|
module Validations
|
|
15
16
|
module ClassMethods
|
|
@@ -151,8 +152,10 @@ module Volt
|
|
|
151
152
|
end.then do
|
|
152
153
|
run_custom_validations
|
|
153
154
|
end.then do
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
if errors.size > 0
|
|
156
|
+
# Return the errors object
|
|
157
|
+
Promise.new.reject(errors)
|
|
158
|
+
end
|
|
156
159
|
end
|
|
157
160
|
end
|
|
158
161
|
|
|
@@ -16,6 +16,17 @@ module Volt
|
|
|
16
16
|
|
|
17
17
|
valid_type = false
|
|
18
18
|
type_restrictions.each do |type_rest|
|
|
19
|
+
if type_rest == Volt::Boolean
|
|
20
|
+
if RUBY_PLATFORM == 'opal'
|
|
21
|
+
type_rest = ::Boolean
|
|
22
|
+
else
|
|
23
|
+
if value == true || value == false
|
|
24
|
+
valid_type = true
|
|
25
|
+
break
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
19
30
|
if value.is_a?(type_rest)
|
|
20
31
|
valid_type = true
|
|
21
32
|
break
|
|
@@ -8,11 +8,11 @@ module Volt
|
|
|
8
8
|
query = {}
|
|
9
9
|
# Check to see if any other documents have this value.
|
|
10
10
|
query[field_name.to_s] = value
|
|
11
|
-
|
|
11
|
+
model_id = model.id
|
|
12
12
|
|
|
13
13
|
# Check if the value is taken
|
|
14
14
|
# TODO: need a way to handle scope for unique
|
|
15
|
-
return Volt.current_app.store.get(model.path[-2]).where(query).first.then do |item|
|
|
15
|
+
return Volt.current_app.store.get(model.path[-2]).where(query) {|m| m.id !~ model_id }.first.then do |item|
|
|
16
16
|
if item
|
|
17
17
|
message = (args.is_a?(Hash) && args[:message]) || 'is already taken'
|
|
18
18
|
|
|
@@ -10,7 +10,11 @@ module Volt
|
|
|
10
10
|
@getter = getter
|
|
11
11
|
@setter = setter
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
if RUBY_PLATFORM == 'opal'
|
|
14
|
+
setup
|
|
15
|
+
else
|
|
16
|
+
setup_server
|
|
17
|
+
end
|
|
14
18
|
end
|
|
15
19
|
|
|
16
20
|
def setup
|
|
@@ -62,6 +66,24 @@ module Volt
|
|
|
62
66
|
|
|
63
67
|
end
|
|
64
68
|
|
|
69
|
+
unless RUBY_PLATFORM == 'opal'
|
|
70
|
+
# Does the setup during server side rendering
|
|
71
|
+
def setup_server
|
|
72
|
+
val = begin
|
|
73
|
+
@context.instance_eval(&@getter)
|
|
74
|
+
rescue => e
|
|
75
|
+
getter_fail(e)
|
|
76
|
+
''
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
node = target.find_by_tag_id(binding_name)
|
|
80
|
+
|
|
81
|
+
if node
|
|
82
|
+
node.attributes[@attribute_name] = val
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
65
87
|
def changed(event = nil)
|
|
66
88
|
case @attribute_name
|
|
67
89
|
when 'value'
|
|
@@ -60,6 +60,13 @@ module Volt
|
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
+
# Also copy the attribute bindings
|
|
64
|
+
bindings.each_pair do |name, binding|
|
|
65
|
+
if name.is_a?(String) && name[0..1] == 'id'
|
|
66
|
+
new_bindings[name] = binding
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
63
70
|
return new_html.join(''), new_bindings
|
|
64
71
|
end
|
|
65
72
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
require 'volt/page/targets/binding_document/html_node'
|
|
2
|
+
require 'volt/page/targets/binding_document/tag_node'
|
|
2
3
|
require 'volt/reactive/eventable'
|
|
3
4
|
|
|
4
5
|
module Volt
|
|
5
|
-
# Component nodes contain an array of
|
|
6
|
+
# Component nodes contain an array of HtmlNodes, TagNodes and ComponentNodes.
|
|
6
7
|
# Instead of providing a full DOM API, component nodes are the branch
|
|
7
|
-
# nodes and html nodes are the leafs. This is all we need to produce
|
|
8
|
+
# nodes and html/tag nodes are the leafs. This is all we need to produce
|
|
8
9
|
# the html from templates outside of a normal dom.
|
|
9
10
|
class ComponentNode < BaseNode
|
|
10
11
|
include Eventable
|
|
@@ -31,7 +32,8 @@ module Volt
|
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
def html=(html)
|
|
34
|
-
|
|
35
|
+
tag_regex = /(\<[^\>]+\>)/
|
|
36
|
+
parts = html.split(tag_regex).reject { |v| v == '' }
|
|
35
37
|
|
|
36
38
|
# Clear current nodes
|
|
37
39
|
@nodes = []
|
|
@@ -40,21 +42,27 @@ module Volt
|
|
|
40
42
|
|
|
41
43
|
parts.each do |part|
|
|
42
44
|
case part
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
45
|
+
when /\<\!\-\- \$[0-9]+ \-\-\>/
|
|
46
|
+
# Open
|
|
47
|
+
binding_id = part.match(/\<\!\-\- \$([0-9]+) \-\-\>/)[1].to_i
|
|
48
|
+
|
|
49
|
+
sub_node = ComponentNode.new(binding_id, current_node, @root || self)
|
|
50
|
+
current_node << sub_node
|
|
51
|
+
current_node = sub_node
|
|
52
|
+
when /\<\!\-\- \$\/[0-9]+ \-\-\>/
|
|
53
|
+
# Close
|
|
54
|
+
# binding_id = part.match(/\<\!\-\- \$\/([0-9]+) \-\-\>/)[1].to_i
|
|
55
|
+
|
|
56
|
+
current_node = current_node.parent
|
|
57
|
+
when /^<\/[-!\:A-Za-z0-9_]+[^>]*>/
|
|
58
|
+
# end tag, just store it as html for performance
|
|
59
|
+
current_node << HtmlNode.new(part)
|
|
60
|
+
when /\<[^\>]+\>/
|
|
61
|
+
# start tag or unary
|
|
62
|
+
current_node << TagNode.new(part)
|
|
63
|
+
else
|
|
64
|
+
# html string
|
|
65
|
+
current_node << HtmlNode.new(part)
|
|
58
66
|
end
|
|
59
67
|
end
|
|
60
68
|
|
|
@@ -92,6 +100,24 @@ module Volt
|
|
|
92
100
|
nil
|
|
93
101
|
end
|
|
94
102
|
|
|
103
|
+
# TODO: This is an inefficient implementation since it has to walk the tree,
|
|
104
|
+
# we should make it so it caches nodes after the first walk (similar to
|
|
105
|
+
# how browsers handle getElementById)
|
|
106
|
+
def find_by_tag_id(tag_id)
|
|
107
|
+
@nodes.each do |node|
|
|
108
|
+
if node.is_a?(ComponentNode)
|
|
109
|
+
# Walk down nodes
|
|
110
|
+
val = node.find_by_tag_id(tag_id)
|
|
111
|
+
return val if val
|
|
112
|
+
elsif node.is_a?(TagNode)
|
|
113
|
+
# Found a matching tag
|
|
114
|
+
return node if node.tag_id == tag_id
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
nil
|
|
119
|
+
end
|
|
120
|
+
|
|
95
121
|
def remove
|
|
96
122
|
@nodes = []
|
|
97
123
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# The tag node represents an html tag with a binding id in it. It provides an
|
|
2
|
+
# api to change attribute values.
|
|
3
|
+
|
|
4
|
+
require 'volt/page/targets/binding_document/base_node'
|
|
5
|
+
|
|
6
|
+
module Volt
|
|
7
|
+
class TagNode < BaseNode
|
|
8
|
+
# We use some of the same parts from the sandlebars parser, but since this
|
|
9
|
+
# has to ship to the client, we only take the parts we need.
|
|
10
|
+
START_TAG = /^<([-!\:A-Za-z0-9_]+)((?:\s+[\w\-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/
|
|
11
|
+
ATTRIBUTES = /([-\:A-Za-z0-9_]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/
|
|
12
|
+
|
|
13
|
+
attr_reader :tag_id
|
|
14
|
+
attr_reader :attributes
|
|
15
|
+
|
|
16
|
+
def initialize(html)
|
|
17
|
+
tag = html.scan(START_TAG).first
|
|
18
|
+
@start_tag = tag[0]
|
|
19
|
+
@attributes = tag[1].scan(ATTRIBUTES).map do |match|
|
|
20
|
+
name = match[0]
|
|
21
|
+
value = match[1] || match[2] || match[3]
|
|
22
|
+
|
|
23
|
+
# Store the tag's id for quick lookup
|
|
24
|
+
if name == 'id'
|
|
25
|
+
@tag_id = value
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
[name, value]
|
|
29
|
+
end.to_h
|
|
30
|
+
@end_tag = tag[2]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def to_html
|
|
34
|
+
attr_str = @attributes.map do |key, value|
|
|
35
|
+
"#{key}=\"#{value}\""
|
|
36
|
+
end.join(' ')
|
|
37
|
+
|
|
38
|
+
"<" + [@start_tag, attr_str, @end_tag].reject(&:blank?).join(' ') + ">"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/volt/page/tasks.rb
CHANGED
|
@@ -30,12 +30,12 @@ module Volt
|
|
|
30
30
|
|
|
31
31
|
def received_message(name, promise_id, *args)
|
|
32
32
|
case name
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
when 'updated'
|
|
34
|
+
notify_updated(*args)
|
|
35
|
+
when 'response'
|
|
36
|
+
response(promise_id, *args)
|
|
37
|
+
when 'reload'
|
|
38
|
+
reload
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
41
|
|
|
@@ -57,6 +57,14 @@ module Volt
|
|
|
57
57
|
Volt.logger.error('Task Response:')
|
|
58
58
|
Volt.logger.error(error)
|
|
59
59
|
|
|
60
|
+
if error.is_a?(String)
|
|
61
|
+
klass, error = error.split(':', 2)
|
|
62
|
+
|
|
63
|
+
if (klass = Object.const_get(klass))
|
|
64
|
+
error = klass.new(error)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
60
68
|
promise.reject(error)
|
|
61
69
|
else
|
|
62
70
|
promise.resolve(result)
|
|
@@ -66,9 +74,9 @@ module Volt
|
|
|
66
74
|
|
|
67
75
|
# Called when the backend sends a notification to change the results of
|
|
68
76
|
# a query.
|
|
69
|
-
def
|
|
77
|
+
def notify_updated(collection, query, *args)
|
|
70
78
|
query_obj = Persistors::ArrayStore.query_pool.lookup(collection, query)
|
|
71
|
-
query_obj.
|
|
79
|
+
query_obj.updated(*args)
|
|
72
80
|
end
|
|
73
81
|
|
|
74
82
|
def reload
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Tracks a channel and a query on a collection. Alerts
|
|
2
|
+
# the listener when the data in the query changes.
|
|
3
|
+
#
|
|
4
|
+
# has_many QuerySubscriptions
|
|
5
|
+
|
|
6
|
+
require 'volt/queries/query_diff'
|
|
7
|
+
require 'volt/queries/query_runner'
|
|
8
|
+
|
|
9
|
+
module Volt
|
|
10
|
+
class LiveQuery
|
|
11
|
+
attr_reader :volt_app, :pool, :collection, :query, :last_data
|
|
12
|
+
|
|
13
|
+
def initialize(volt_app, pool, data_store, collection, query)
|
|
14
|
+
@volt_app = volt_app
|
|
15
|
+
@pool = pool
|
|
16
|
+
@collection = collection
|
|
17
|
+
@query = query
|
|
18
|
+
|
|
19
|
+
@query_runner = QueryRunner.new(data_store, collection, query)
|
|
20
|
+
# Grab the associations from the query runner
|
|
21
|
+
@associations = @query_runner.associations
|
|
22
|
+
|
|
23
|
+
@query_subscriptions = {}
|
|
24
|
+
@data_store = data_store
|
|
25
|
+
|
|
26
|
+
# initial run
|
|
27
|
+
@last_data = run
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Decrements the count on the LiveQueryPool. This is called when a
|
|
31
|
+
# QuerySubscrption no longer is using this LiveQuery.
|
|
32
|
+
def remove_reference
|
|
33
|
+
@pool.remove(@collection, @query)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def query_subscription_for_channel(channel)
|
|
37
|
+
@query_subscriptions[channel] ||= begin
|
|
38
|
+
QuerySubscription.new(self, channel)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def remove_query_subscription(channel)
|
|
43
|
+
@query_subscriptions.delete(channel)
|
|
44
|
+
|
|
45
|
+
if @query_subscriptions.empty?
|
|
46
|
+
# No more query_subscriptions, this means no one is using this query,
|
|
47
|
+
# remove it from the pool
|
|
48
|
+
|
|
49
|
+
# Reduce the count on the live query
|
|
50
|
+
begin
|
|
51
|
+
remove_reference
|
|
52
|
+
rescue Volt::GenericPoolDeleteException => e
|
|
53
|
+
# ignore
|
|
54
|
+
Volt.logger.error e.inspect
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def run
|
|
60
|
+
@query_runner.run
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def update(skip_channel=nil)
|
|
65
|
+
new_data = run
|
|
66
|
+
|
|
67
|
+
if new_data.is_a?(Array)
|
|
68
|
+
# Diff the new data
|
|
69
|
+
diff = QueryDiff.new(@last_data, @associations).run(new_data)
|
|
70
|
+
|
|
71
|
+
notify_updated(skip_channel, diff)
|
|
72
|
+
else
|
|
73
|
+
# When working with queries that return a value, we don't diff, we just
|
|
74
|
+
# push an update operation
|
|
75
|
+
notify_updated(skip_channel, {'u' => new_data})
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
@last_data = new_data
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def notify_updated(skip_channel, diff)
|
|
82
|
+
# TODO: We should be able to mostly skip our own channel
|
|
83
|
+
# notify!(skip_channel) do |query_subscription|
|
|
84
|
+
notify! do |query_subscription|
|
|
85
|
+
query_subscription.notify_updated(diff)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Runs through each query subscription, yielding all except the optional
|
|
90
|
+
# one with skip_channel.
|
|
91
|
+
|
|
92
|
+
def notify!(skip_channel = nil)
|
|
93
|
+
query_subs = @query_subscriptions
|
|
94
|
+
if skip_channel
|
|
95
|
+
query_subs = query_subs.reject { |channel, query_sub| channel == skip_channel }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
query_subs.values.each do |query_sub|
|
|
99
|
+
yield(query_sub)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def inspect
|
|
105
|
+
"<#{self.class} #{@collection}: #{@query.inspect}>"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
end
|
|
109
|
+
end
|