volt 0.9.6 → 0.9.7.pre2

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
@@ -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
- # Return the errors object
155
- errors
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
- query['id'] = { '$ne' => model.id }
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
- setup
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 both HtmlNodes and ComponentNodes.
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
- parts = html.split(/(\<\!\-\- \$\/?[0-9]+ \-\-\>)/).reject { |v| v == '' }
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
- when /\<\!\-\- \$[0-9]+ \-\-\>/
44
- # Open
45
- binding_id = part.match(/\<\!\-\- \$([0-9]+) \-\-\>/)[1].to_i
46
-
47
- sub_node = ComponentNode.new(binding_id, current_node, @root || self)
48
- current_node << sub_node
49
- current_node = sub_node
50
- when /\<\!\-\- \$\/[0-9]+ \-\-\>/
51
- # Close
52
- # binding_id = part.match(/\<\!\-\- \$\/([0-9]+) \-\-\>/)[1].to_i
53
-
54
- current_node = current_node.parent
55
- else
56
- # html string
57
- current_node << HtmlNode.new(part)
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
@@ -30,12 +30,12 @@ module Volt
30
30
 
31
31
  def received_message(name, promise_id, *args)
32
32
  case name
33
- when 'added', 'removed', 'updated', 'changed'
34
- notify_query(name, *args)
35
- when 'response'
36
- response(promise_id, *args)
37
- when 'reload'
38
- reload
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 notify_query(method_name, collection, query, *args)
77
+ def notify_updated(collection, query, *args)
70
78
  query_obj = Persistors::ArrayStore.query_pool.lookup(collection, query)
71
- query_obj.send(method_name, *args)
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