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.
Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/CHANGELOG.md +26 -0
  4. data/Gemfile +6 -1
  5. data/README.md +2 -0
  6. data/Rakefile +1 -0
  7. data/app/volt/models/active_volt_instance.rb +8 -6
  8. data/app/volt/models/user.rb +11 -2
  9. data/app/volt/models/volt_app_property.rb +8 -0
  10. data/app/volt/tasks/query_tasks.rb +23 -31
  11. data/app/volt/tasks/store_tasks.rb +2 -2
  12. data/app/volt/tasks/volt_admin_tasks.rb +24 -0
  13. data/docs/UPGRADE_GUIDE.md +6 -0
  14. data/lib/volt.rb +19 -12
  15. data/lib/volt/boot.rb +1 -0
  16. data/lib/volt/cli.rb +19 -8
  17. data/lib/volt/cli/console.rb +0 -1
  18. data/lib/volt/cli/generators.rb +14 -3
  19. data/lib/volt/cli/migrate.rb +26 -0
  20. data/lib/volt/config.rb +17 -4
  21. data/lib/volt/controllers/http_controller.rb +12 -0
  22. data/lib/volt/data_stores/base_adaptor_client.rb +2 -2
  23. data/lib/volt/data_stores/base_adaptor_server.rb +2 -0
  24. data/lib/volt/data_stores/data_store.rb +20 -14
  25. data/lib/volt/extra_core/class.rb +28 -14
  26. data/lib/volt/extra_core/hash.rb +5 -0
  27. data/lib/volt/extra_core/string.rb +3 -1
  28. data/lib/volt/helpers/time.rb +9 -43
  29. data/lib/volt/helpers/time/calculations.rb +204 -0
  30. data/lib/volt/helpers/time/distance.rb +63 -0
  31. data/lib/volt/helpers/time/duration.rb +71 -0
  32. data/lib/volt/helpers/time/local_calculations.rb +49 -0
  33. data/lib/volt/helpers/time/local_volt_time.rb +23 -0
  34. data/lib/volt/helpers/time/numeric.rb +59 -0
  35. data/lib/volt/helpers/time/volt_time.rb +170 -0
  36. data/lib/volt/models.rb +5 -0
  37. data/lib/volt/models/array_model.rb +33 -6
  38. data/lib/volt/models/associations.rb +146 -23
  39. data/lib/volt/models/buffer.rb +38 -41
  40. data/lib/volt/models/cursor.rb +15 -0
  41. data/lib/volt/models/errors.rb +11 -0
  42. data/lib/volt/models/field_helpers.rb +108 -68
  43. data/lib/volt/models/helpers/array_model.rb +4 -0
  44. data/lib/volt/models/helpers/base.rb +8 -1
  45. data/lib/volt/models/helpers/change_helpers.rb +31 -12
  46. data/lib/volt/models/helpers/defaults.rb +15 -0
  47. data/lib/volt/models/location.rb +20 -6
  48. data/lib/volt/models/migrations/migration.rb +23 -0
  49. data/lib/volt/models/migrations/migration_runner.rb +146 -0
  50. data/lib/volt/models/model.rb +38 -1
  51. data/lib/volt/models/permissions.rb +8 -1
  52. data/lib/volt/models/persistors/array_store.rb +87 -8
  53. data/lib/volt/models/persistors/base.rb +19 -0
  54. data/lib/volt/models/persistors/model_store.rb +1 -1
  55. data/lib/volt/models/persistors/page.rb +4 -1
  56. data/lib/volt/models/persistors/query/query_identifier.rb +102 -0
  57. data/lib/volt/models/persistors/query/query_listener.rb +57 -12
  58. data/lib/volt/models/root_models/root_models.rb +19 -0
  59. data/lib/volt/models/url.rb +11 -2
  60. data/lib/volt/models/validations/validations.rb +5 -2
  61. data/lib/volt/models/validators/type_validator.rb +11 -0
  62. data/lib/volt/models/validators/unique_validator.rb +2 -2
  63. data/lib/volt/page/bindings/attribute_binding.rb +23 -1
  64. data/lib/volt/page/targets/attribute_section.rb +7 -0
  65. data/lib/volt/page/targets/binding_document/component_node.rb +44 -18
  66. data/lib/volt/page/targets/binding_document/tag_node.rb +41 -0
  67. data/lib/volt/page/tasks.rb +16 -8
  68. data/lib/volt/queries/live_query.rb +109 -0
  69. data/lib/volt/queries/live_query_pool.rb +58 -0
  70. data/lib/volt/queries/live_subquery.rb +0 -0
  71. data/lib/volt/queries/query_association_splitter.rb +31 -0
  72. data/lib/volt/queries/query_diff.rb +100 -0
  73. data/lib/volt/queries/query_runner.rb +110 -0
  74. data/lib/volt/queries/query_subscription.rb +80 -0
  75. data/lib/volt/queries/query_subscription_pool.rb +37 -0
  76. data/lib/volt/reactive/eventable.rb +8 -0
  77. data/lib/volt/reactive/reactive_array.rb +0 -4
  78. data/lib/volt/router/routes.rb +81 -31
  79. data/lib/volt/server/message_bus/base_message_bus.rb +9 -3
  80. data/lib/volt/server/message_bus/peer_to_peer.rb +6 -6
  81. data/lib/volt/server/message_bus/peer_to_peer/server_tracker.rb +1 -1
  82. data/lib/volt/server/middleware/default_middleware_stack.rb +12 -8
  83. data/lib/volt/server/rack/component_paths.rb +31 -4
  84. data/lib/volt/server/rack/http_content_types.rb +62 -0
  85. data/lib/volt/server/rack/http_resource.rb +1 -1
  86. data/lib/volt/server/rack/index_files.rb +8 -1
  87. data/lib/volt/server/rack/opal_files.rb +16 -1
  88. data/lib/volt/server/rack/sprockets_helpers_setup.rb +32 -1
  89. data/lib/volt/server/socket_connection_handler.rb +16 -7
  90. data/lib/volt/server/template_handlers/sprockets_component_handler.rb +5 -3
  91. data/lib/volt/spec/capybara.rb +4 -3
  92. data/lib/volt/spec/setup.rb +5 -0
  93. data/lib/volt/tasks/dispatcher.rb +3 -1
  94. data/lib/volt/utils/data_transformer.rb +4 -4
  95. data/lib/volt/utils/ejson.rb +19 -6
  96. data/lib/volt/utils/promise_extensions.rb +1 -1
  97. data/lib/volt/utils/time_opal_patch.rb +749 -0
  98. data/lib/volt/utils/time_patch.rb +11 -4
  99. data/lib/volt/version.rb +1 -1
  100. data/lib/volt/volt/app.rb +19 -11
  101. data/lib/volt/volt/properties.rb +24 -0
  102. data/lib/volt/volt/server_setup/app.rb +30 -7
  103. data/lib/volt/volt/users.rb +15 -3
  104. data/spec/apps/kitchen_sink/Gemfile +5 -1
  105. data/spec/apps/kitchen_sink/app/main/config/routes.rb +1 -0
  106. data/spec/apps/kitchen_sink/app/main/controllers/save_controller.rb +1 -1
  107. data/spec/apps/kitchen_sink/app/main/controllers/server/simple_http_controller.rb +4 -0
  108. data/spec/apps/kitchen_sink/app/main/controllers/todos_controller.rb +4 -2
  109. data/spec/apps/kitchen_sink/app/main/models/post.rb +0 -1
  110. data/spec/apps/kitchen_sink/app/main/models/todo.rb +4 -0
  111. data/spec/apps/kitchen_sink/app/main/views/mailers/reset_password.html +10 -0
  112. data/spec/apps/kitchen_sink/app/main/views/todos/index.html +2 -0
  113. data/spec/apps/kitchen_sink/config/app.rb +2 -0
  114. data/spec/apps/migrations/config/db/migrations/1445111704_migration1.rb +7 -0
  115. data/spec/apps/migrations/config/db/migrations/1445113517_migration2.rb +7 -0
  116. data/spec/apps/migrations/config/db/migrations/1445115200_migration3.rb +7 -0
  117. data/spec/extra_core/class_spec.rb +10 -0
  118. data/spec/helpers/distance_spec.rb +35 -0
  119. data/spec/helpers/duration_spec.rb +160 -0
  120. data/spec/helpers/volt_time_spec.rb +275 -0
  121. data/spec/integration/callbacks_spec.rb +2 -1
  122. data/spec/integration/http_endpoints_spec.rb +4 -0
  123. data/spec/integration/save_spec.rb +1 -1
  124. data/spec/integration/todos_spec.rb +7 -5
  125. data/spec/models/array_model_spec.rb +17 -3
  126. data/spec/models/associations_spec.rb +48 -1
  127. data/spec/models/field_helpers_spec.rb +7 -3
  128. data/spec/models/migrations/migration_runner_spec.rb +69 -0
  129. data/spec/models/model_spec.rb +42 -8
  130. data/spec/models/permissions_spec.rb +20 -8
  131. data/spec/models/persistors/array_store_spec.rb +18 -0
  132. data/spec/models/persistors/page_spec.rb +15 -10
  133. data/spec/models/persistors/store_spec.rb +13 -3
  134. data/spec/models/url_spec.rb +4 -3
  135. data/spec/models/user_spec.rb +6 -3
  136. data/spec/models/user_validation_spec.rb +3 -3
  137. data/spec/models/validations_spec.rb +4 -0
  138. data/spec/models/validators/block_validations_spec.rb +9 -5
  139. data/spec/models/validators/email_validator_spec.rb +2 -0
  140. data/spec/models/validators/lifecycle_callbacks_spec.rb +86 -0
  141. data/spec/models/validators/unique_validator_spec.rb +1 -0
  142. data/spec/page/path_string_renderer_spec.rb +5 -0
  143. data/spec/queries/live_query_spec.rb +16 -0
  144. data/spec/queries/query_association_splitter_spec.rb +14 -0
  145. data/spec/queries/query_diff_spec.rb +132 -0
  146. data/spec/queries/query_identifier_spec.rb +98 -0
  147. data/spec/queries/query_runner_spec.rb +63 -0
  148. data/spec/queries/query_tracker_spec.rb +141 -0
  149. data/spec/router/routes_spec.rb +52 -21
  150. data/spec/server/middleware/rack_content_types_spec.rb +78 -0
  151. data/spec/server/rack/asset_files_spec.rb +38 -30
  152. data/spec/spec_helper.rb +8 -0
  153. data/spec/utils/ejson_spec.rb +9 -8
  154. data/spec/utils/ejson_volt_time_spec.rb +65 -0
  155. data/templates/migration/migration.rb.tt +9 -0
  156. data/templates/newgem/gitignore.tt +1 -0
  157. data/templates/project/Gemfile.tt +19 -2
  158. data/templates/project/README.md.tt +6 -1
  159. data/templates/project/app/main/config/dependencies.rb +6 -0
  160. data/templates/project/config/app.rb.tt +18 -4
  161. data/volt.gemspec +2 -2
  162. metadata +73 -16
  163. data/app/volt/tasks/live_query/live_query_pool.rb +0 -48
  164. data/app/volt/tasks/live_query/query_tracker.rb +0 -92
  165. data/spec/tasks/live_query_spec.rb +0 -18
  166. data/spec/tasks/query_tasks.rb +0 -7
  167. 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)
@@ -24,10 +24,6 @@ module Volt
24
24
  @array.send(method_name, *args, &block)
25
25
  end
26
26
 
27
- def ==(*args)
28
- @array.==(*args)
29
- end
30
-
31
27
  # At the moment, each just passes through.
32
28
  def each(&block)
33
29
  @array.each(&block)