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