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
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require 'volt/queries/live_query'
|
|
2
|
+
require 'volt/utils/generic_pool'
|
|
3
|
+
|
|
4
|
+
# LiveQueryPool runs on the server and keeps track of all outstanding
|
|
5
|
+
# queries.
|
|
6
|
+
module Volt
|
|
7
|
+
class LiveQueryPool < Volt::GenericPool
|
|
8
|
+
attr_reader :volt_app
|
|
9
|
+
|
|
10
|
+
def initialize(data_store, volt_app)
|
|
11
|
+
@data_store = data_store
|
|
12
|
+
@volt_app = volt_app
|
|
13
|
+
super()
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def lookup(collection, query)
|
|
17
|
+
super(collection, query)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def updated_collection(collection, skip_channel, from_message_bus=false)
|
|
21
|
+
# collection = collection.to_sym
|
|
22
|
+
lookup_all(collection).each do |live_query|
|
|
23
|
+
live_query.update(skip_channel)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
msg_bus = @volt_app.message_bus
|
|
27
|
+
if !from_message_bus && collection != 'active_volt_instances' && msg_bus
|
|
28
|
+
msg_bus.publish('volt_collection_update', collection)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Show a live updating list of the current live queries
|
|
33
|
+
if RUBY_PLATFORM == 'opal'
|
|
34
|
+
def live_log
|
|
35
|
+
Thread.new do
|
|
36
|
+
loop do
|
|
37
|
+
puts "------ live queries ------"
|
|
38
|
+
@pool.each do |live_query|
|
|
39
|
+
puts live_query.inspect
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
sleep 2
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# Creates the live query if it doesn't exist, and stores it so it
|
|
51
|
+
# can be found later.
|
|
52
|
+
def create(collection, query = {})
|
|
53
|
+
# collection = collection.to_sym
|
|
54
|
+
# If not already setup, create a new one for this collection/query
|
|
55
|
+
LiveQuery.new(@volt_app, self, @data_store, collection, query)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# The QueryAssociationSplitter takes in a query and removes any .includes, and
|
|
2
|
+
# returns the new query, and an hash of association parts.
|
|
3
|
+
#
|
|
4
|
+
# Example:
|
|
5
|
+
# QueryAssociationSplitter.split([['where', {name: 'Bob'}], ['includes', [:posts, [:posts, :comments], :links]]])
|
|
6
|
+
# => ['where', {name: 'Bob'}], {:posts=>{:comments=>{}}, :links=>{}}]
|
|
7
|
+
module Volt
|
|
8
|
+
class QueryAssociationSplitter
|
|
9
|
+
def self.split(query)
|
|
10
|
+
new_query = []
|
|
11
|
+
associations = {}
|
|
12
|
+
query.each do |method, *args|
|
|
13
|
+
method = method.to_sym
|
|
14
|
+
if method == :includes
|
|
15
|
+
args.flatten(1).each do |path|
|
|
16
|
+
path = [path].flatten
|
|
17
|
+
|
|
18
|
+
node = associations
|
|
19
|
+
path.each do |part|
|
|
20
|
+
node = (node[part] ||= {})
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
else
|
|
24
|
+
new_query << [method, *args]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
return new_query, associations
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# QueryDiff takes the results of a query and compares it to the previous
|
|
2
|
+
# run. It builds a diff that can be used to update, insert, and move models
|
|
3
|
+
# to bring things up to date on the client.
|
|
4
|
+
|
|
5
|
+
module Volt
|
|
6
|
+
class QueryDiff
|
|
7
|
+
# @param - initial run of the query(s)
|
|
8
|
+
# @param - is an array of symbols (or array of arrays of symbols) that
|
|
9
|
+
# represent the associations:
|
|
10
|
+
# Example: [:comments, [:comments, :user]]
|
|
11
|
+
def initialize(start_data, associations=[])
|
|
12
|
+
@old_data = start_data
|
|
13
|
+
@old_ids = start_data.map {|v| v[:id] }
|
|
14
|
+
@old_data_hash = hash_data(start_data)
|
|
15
|
+
|
|
16
|
+
@associations = associations
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# returns the data keyed by id for faster lookup
|
|
20
|
+
def hash_data(array)
|
|
21
|
+
Hash[array.map { |r| [r[:id], r] }]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def run(new_data)
|
|
25
|
+
@new_data = new_data
|
|
26
|
+
@new_data_hash = hash_data(new_data)
|
|
27
|
+
@new_ids = new_data.map {|v| v[:id] }
|
|
28
|
+
|
|
29
|
+
@diff = []
|
|
30
|
+
|
|
31
|
+
detect_removed
|
|
32
|
+
detect_added_and_moved
|
|
33
|
+
detect_changed
|
|
34
|
+
|
|
35
|
+
@old_data = new_data
|
|
36
|
+
@old_data_hash = @new_data_hash
|
|
37
|
+
@old_ids = @new_ids
|
|
38
|
+
|
|
39
|
+
# return diff
|
|
40
|
+
@diff
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def detect_removed
|
|
44
|
+
# Removed models
|
|
45
|
+
removed_ids = @old_ids - @new_ids
|
|
46
|
+
if removed_ids.size > 0
|
|
47
|
+
removed_ids.each do |r_id|
|
|
48
|
+
@diff << ['r', r_id]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Update @old_ids to relect the removed
|
|
53
|
+
@old_ids &= @new_ids
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Loop through the new list, tracking in the old, notifies of any that
|
|
57
|
+
# have been added or moved.
|
|
58
|
+
def detect_added_and_moved
|
|
59
|
+
previous_index = 0
|
|
60
|
+
|
|
61
|
+
# we'll mutate the old_ids as we move through them to make it easier.
|
|
62
|
+
old_ids = @old_ids.dup
|
|
63
|
+
@new_ids.each_with_index do |id, index|
|
|
64
|
+
if (cur_previous = old_ids[previous_index]) && cur_previous == id
|
|
65
|
+
# Same in both previous and new
|
|
66
|
+
previous_index += 1
|
|
67
|
+
next
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# We have an item that didn't match the current position's previous
|
|
71
|
+
# TODO: make a hash so we don't have to do include?
|
|
72
|
+
if old_ids.include?(id)
|
|
73
|
+
# The location from the previous has changed, move to correct location.
|
|
74
|
+
|
|
75
|
+
# Remove from old_idss, as it will be moved and we will be past it.
|
|
76
|
+
old_ids.delete(id)
|
|
77
|
+
@diff << ['m', id, index]
|
|
78
|
+
else
|
|
79
|
+
# Check for inserts
|
|
80
|
+
# TODO: Faster lookup
|
|
81
|
+
data = @new_data_hash[id]
|
|
82
|
+
@diff << ['i', index, data]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Finds all items in the previous results that have new values, and alerts
|
|
88
|
+
# of changes.
|
|
89
|
+
def detect_changed
|
|
90
|
+
not_added_or_removed = @old_ids & @new_ids
|
|
91
|
+
|
|
92
|
+
not_added_or_removed.each do |id|
|
|
93
|
+
if @old_data_hash[id] != (data = @new_data_hash[id])
|
|
94
|
+
# Data hash changed
|
|
95
|
+
@diff << ['c', id, data]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# The QueryRunner takes in a query and runs the query and any subqueries
|
|
2
|
+
# needed to return the full results for an end users query. .includes will be
|
|
3
|
+
# parsed out (using QueryAssociationSplitter).
|
|
4
|
+
#
|
|
5
|
+
# When #run is called, it returns an Array of Hashes with the associations
|
|
6
|
+
# filled in.
|
|
7
|
+
|
|
8
|
+
require 'volt/queries/query_association_splitter'
|
|
9
|
+
|
|
10
|
+
module Volt
|
|
11
|
+
class QueryRunner
|
|
12
|
+
attr_reader :associations
|
|
13
|
+
|
|
14
|
+
def initialize(data_store, collection, query)
|
|
15
|
+
@data_store = data_store
|
|
16
|
+
|
|
17
|
+
@collection = collection
|
|
18
|
+
# query and associations are immutable
|
|
19
|
+
@query, @associations = QueryAssociationSplitter.split(query)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Runs the query and
|
|
23
|
+
def run
|
|
24
|
+
# When running through the query, run subqueries for any associations
|
|
25
|
+
|
|
26
|
+
# First run the root query
|
|
27
|
+
data = @data_store.query(@collection, @query)
|
|
28
|
+
|
|
29
|
+
parent_model = Volt::Model.class_at_path([@collection])
|
|
30
|
+
|
|
31
|
+
run_associations(data, parent_model, @associations)
|
|
32
|
+
|
|
33
|
+
data
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @param - the data hash for the root node
|
|
37
|
+
# @param - the path where the assocation data should be matched
|
|
38
|
+
# @param - a hash with the parts for the association
|
|
39
|
+
def run_associations(data, parent_model, associations)
|
|
40
|
+
associations.each_pair do |assoc, sub_assocs|
|
|
41
|
+
run_association(data, parent_model, assoc, sub_assocs)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def run_association(data, parent_model, assoc, sub_assocs)
|
|
46
|
+
assoc = assoc.to_sym
|
|
47
|
+
# Make another query to load in the association's data
|
|
48
|
+
# .where(post_id: parent_ids)
|
|
49
|
+
assoc_data = parent_model.associations[assoc.to_sym]
|
|
50
|
+
|
|
51
|
+
unless assoc_data
|
|
52
|
+
raise "`#{assoc}` was used in .includes(..), but is not a valid association"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
foreign_key, local_key, collection = assoc_data.mfetch(
|
|
56
|
+
:foreign_key, :local_key, :collection
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Lookup the local_key from the association on the models
|
|
60
|
+
parent_ids = ids(data, local_key)
|
|
61
|
+
|
|
62
|
+
subquery = [[:where, {foreign_key => parent_ids}]]
|
|
63
|
+
results = @data_store.query(collection, subquery)
|
|
64
|
+
|
|
65
|
+
# For to many associations, we map to an array by id
|
|
66
|
+
results_by_assoc_id = if assoc_data[:to_many]
|
|
67
|
+
id_map_array(results, foreign_key)
|
|
68
|
+
else
|
|
69
|
+
id_map(results, foreign_key)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Insert data into the original results
|
|
73
|
+
data.each do |row|
|
|
74
|
+
# Assign a field on the row for the child data
|
|
75
|
+
row[assoc] = results_by_assoc_id[row[local_key]]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# unless sub_associations == {}
|
|
79
|
+
# # There are more associations off of this one
|
|
80
|
+
|
|
81
|
+
# end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
# returns an array of ids
|
|
88
|
+
def ids(data, map_by=:id)
|
|
89
|
+
data.map {|v| v[map_by]}
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Takes in an array of hashes, and returns a hash of hashes keyed by id
|
|
93
|
+
def id_map(data, map_by=:id)
|
|
94
|
+
data.map {|v| [v[map_by], v] }.to_h
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# like id_map, except it returns an array for multiple maps to the same id
|
|
98
|
+
def id_map_array(data, map_by=:id)
|
|
99
|
+
results = {}
|
|
100
|
+
data.each do |row|
|
|
101
|
+
key = row[map_by]
|
|
102
|
+
results[key] ||= []
|
|
103
|
+
results[key] << row
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
results
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# The QuerySubscription keeps track of a LiveQuerry and a Channel which should be
|
|
2
|
+
# updated when the LiveQuery changes.
|
|
3
|
+
|
|
4
|
+
module Volt
|
|
5
|
+
class QuerySubscription
|
|
6
|
+
def initialize(live_query, channel)
|
|
7
|
+
@live_query = live_query
|
|
8
|
+
@channel = channel
|
|
9
|
+
|
|
10
|
+
if @channel
|
|
11
|
+
chan_subs = @live_query.volt_app.channel_query_subscriptions
|
|
12
|
+
chan_subs[@channel] ||= {}
|
|
13
|
+
chan_subs[@channel][self] = true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
@old_data = []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def remove
|
|
20
|
+
# Remove from channel
|
|
21
|
+
if @channel
|
|
22
|
+
chan_subs = @live_query.volt_app.channel_query_subscriptions
|
|
23
|
+
subs_for_chan = chan_subs[@channel]
|
|
24
|
+
subs_for_chan.delete(self) if subs_for_chan
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Remove from the pool
|
|
28
|
+
@live_query.remove_query_subscription(@channel)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def initial_data
|
|
32
|
+
# puts "INITIAL DATA: #{@live_query.last_data.inspect}"
|
|
33
|
+
begin
|
|
34
|
+
# Filter the data first if we are working with models
|
|
35
|
+
filter_data(@live_query.last_data)
|
|
36
|
+
rescue => e
|
|
37
|
+
puts e.inspect
|
|
38
|
+
puts e.backtrace.join("\n")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def notify_updated(diff)
|
|
43
|
+
# puts "UPD: #{@live_query.collection.inspect}, #{@live_query.query.inspect}, #{diff.inspect}"
|
|
44
|
+
@channel.send_message('updated', nil, @live_query.collection, @live_query.query, diff)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Filters data based on the user's permissions
|
|
48
|
+
def filter_data(data)
|
|
49
|
+
if data.is_a?(Array)
|
|
50
|
+
data.map do |data|
|
|
51
|
+
model = model_for_filter(data)
|
|
52
|
+
|
|
53
|
+
# @channel might be nil
|
|
54
|
+
Volt.as_user(@channel.try(:user_id)) do
|
|
55
|
+
model.filtered_attributes.sync
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
else
|
|
59
|
+
data
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Takes in data to be sent to the client and sets up a model to test
|
|
64
|
+
# field permissions against
|
|
65
|
+
def model_for_filter(data)
|
|
66
|
+
klass = Volt::Model.class_at_path([@live_query.collection])
|
|
67
|
+
model = nil
|
|
68
|
+
|
|
69
|
+
# Skip read validations when loading the model, no need to check read when checking
|
|
70
|
+
# permissions.
|
|
71
|
+
# TODO: We should probably document the possibility of data leak here, though really you
|
|
72
|
+
# shouldn't be storing anything inside of the permissions block.
|
|
73
|
+
Volt::Model.no_validate do
|
|
74
|
+
model = klass.new(data, {}, :loaded)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
model
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'volt/queries/query_subscription'
|
|
2
|
+
require 'volt/utils/generic_pool'
|
|
3
|
+
|
|
4
|
+
# QuerySubscriptionPool keeps track of QuerySubscriptions
|
|
5
|
+
module Volt
|
|
6
|
+
class QuerySubscriptionPool
|
|
7
|
+
def initialize(volt_app)
|
|
8
|
+
@pool = {}
|
|
9
|
+
@volt_app = volt_app
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def lookup(*args)
|
|
13
|
+
@pool[[*args]] ||= begin
|
|
14
|
+
create(*args)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def remove(*args)
|
|
19
|
+
@pool.delete([*args])
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def clear
|
|
23
|
+
@pool.values.each(&:remove)
|
|
24
|
+
@pool = {}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
# Creates the live query if it doesn't exist, and stores it so it
|
|
30
|
+
# can be found later.
|
|
31
|
+
def create(collection, query, channel)
|
|
32
|
+
# collection = collection.to_sym
|
|
33
|
+
# If not already setup, create a new one for this collection/query
|
|
34
|
+
QuerySubscription.new(@volt_app, self, collection, query, channel)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -70,6 +70,14 @@ module Volt
|
|
|
70
70
|
listener
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
+
# Same as on, but automatically removes itself after the call
|
|
74
|
+
def once(*events, &callback)
|
|
75
|
+
list = on(*events) do |*args, &block|
|
|
76
|
+
callback.call(*args, &block)
|
|
77
|
+
list.remove
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
73
81
|
# Triggers event on the class the module was includeded. Any .on listeners
|
|
74
82
|
# will have their block called passing in *args.
|
|
75
83
|
def trigger!(event, *args)
|