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
@@ -13,6 +13,7 @@ require 'volt/models/validators/user_validation'
13
13
  require 'volt/models/helpers/dirty'
14
14
  require 'volt/models/helpers/listener_tracker'
15
15
  require 'volt/models/helpers/change_helpers'
16
+ require 'volt/models/helpers/defaults'
16
17
  require 'volt/models/permissions'
17
18
  require 'volt/models/associations'
18
19
  require 'volt/reactive/class_eventable'
@@ -43,6 +44,7 @@ module Volt
43
44
  include FieldHelpers
44
45
  include UserValidatorHelpers
45
46
  include Models::Helpers::Dirty
47
+ include Models::Helpers::Defaults
46
48
  include ClassEventable
47
49
  include Modes
48
50
  include Models::Helpers::ListenerTracker
@@ -93,6 +95,8 @@ module Volt
93
95
  change_state_to(:loaded_state, initial_state || :loaded, false)
94
96
  end
95
97
 
98
+ setup_defaults
99
+
96
100
  # Trigger the new event, pass in :new
97
101
  trigger!(:new, :new)
98
102
  end
@@ -288,6 +292,10 @@ module Volt
288
292
  opts = @options.merge(parent: self, path: path + [method_name])
289
293
 
290
294
  if method_name.plural?
295
+ if self.class != Volt::Model
296
+ opts[:associate] = self.class.collection_name
297
+ end
298
+
291
299
  return new_array_model([], opts)
292
300
  else
293
301
  return new_model({}, opts)
@@ -306,12 +314,27 @@ module Volt
306
314
  options = options.dup
307
315
  options[:query] = []
308
316
 
317
+ check_require_model_class(options)
318
+
309
319
  Volt::ArrayModel.class_at_path(options[:path]).new(attributes, options)
310
320
  end
311
321
 
322
+ # For collections that don't allow on the fly models, we check to see if
323
+ # a model class has been defined.
324
+ def check_require_model_class(options)
325
+ model_klass = Volt::Model.class_at_path(options[:path])
326
+
327
+ if model_klass == Volt::Model && @persistor && !@persistor.on_the_fly_collections?
328
+ raise "The `#{root.repo_name}` repo (at `#{options[:path].join('.')}`) does not allow on the fly collections, create a class for the model before using it."
329
+ end
330
+ end
331
+
312
332
  def inspect
313
333
  Computation.run_without_tracking do
314
334
  str = "#<#{self.class}"
335
+
336
+
337
+ str += ":(buffer)" if buffer?
315
338
  # str += ":#{object_id}"
316
339
 
317
340
  # First, select all of the non-ArrayModel values
@@ -319,7 +342,7 @@ module Volt
319
342
 
320
343
  # Show the :id first, then sort the rest of the attributes
321
344
  id = attrs.delete(:id)
322
- id = id[0..3] + '..' + id[-4..-1] if id
345
+ id = id[0..3] + '..' + id[-4..-1] if id && id.size > 5
323
346
 
324
347
  attrs = attrs.sort
325
348
  attrs.insert(0, [:id, id]) if id
@@ -354,6 +377,20 @@ module Volt
354
377
  to_h.to_json
355
378
  end
356
379
 
380
+ # return the collection name for this model class
381
+ def self.collection_name
382
+ name = @collection_name || self.name
383
+ if name
384
+ name.underscore.pluralize.to_sym
385
+ else
386
+ raise "Class does not have a name, assign one with collection_name or use a named class."
387
+ end
388
+ end
389
+
390
+ def self.set_collection_name(collection_name)
391
+ @collection_name = collection_name.to_s
392
+ end
393
+
357
394
  # Update tries to update the model and returns
358
395
  def update(attrs)
359
396
  old_attrs = @attributes.dup
@@ -14,7 +14,7 @@ module Volt
14
14
  belongs_to key.to_s.gsub(pattern, '')
15
15
  else
16
16
  raise "You tried to auto associate a model using #{key}, but #{key} "\
17
- "does not end in `_id`"
17
+ "does not end in `_id`"
18
18
  end # When the model is created, assign it the user_id (if the user is logged in)
19
19
  on(:new) do
20
20
  # Only assign the user_id if there isn't already one and the user is logged in.
@@ -173,6 +173,13 @@ module Volt
173
173
  res.each do |key, value|
174
174
  if value.is_a?(Model)
175
175
  value = value.filtered_attributes
176
+ elsif value.is_a?(ArrayModel)
177
+ new_val = []
178
+ value.all.each do |sub_model|
179
+ new_val << sub_model.filtered_attributes.sync
180
+ end
181
+
182
+ value = new_val
176
183
  end
177
184
  keys << key
178
185
  values << value
@@ -1,13 +1,14 @@
1
1
  require 'volt/models/persistors/store'
2
2
  require 'volt/models/persistors/store_state'
3
3
  require 'volt/models/persistors/query/query_listener_pool'
4
+ require 'volt/models/persistors/query/query_identifier'
4
5
  require 'volt/utils/timers'
5
6
 
6
7
  module Volt
7
8
  module Persistors
8
9
  class ArrayStore < Store
9
10
  include StoreState
10
-
11
+ include ReactiveAccessors
11
12
 
12
13
  @@query_pool = QueryListenerPool.new
13
14
 
@@ -72,6 +73,7 @@ module Volt
72
73
 
73
74
  # Called by child models to track their listeners
74
75
  def listener_removed
76
+ # puts "LIST REMOVE: #{inspect}"
75
77
  @listener_event_counter.remove
76
78
  end
77
79
 
@@ -137,7 +139,7 @@ module Volt
137
139
  if parent && !@model.is_a?(Cursor) && (attrs = parent.attributes) && attrs[:id]
138
140
  query = query.dup
139
141
 
140
- query << [:find, { :"#{@model.path[-3].singularize}_id" => attrs[:id] }]
142
+ query << [:where, { :"#{@model.path[-3].singularize}_id" => attrs[:id] }]
141
143
  end
142
144
  end
143
145
 
@@ -157,16 +159,89 @@ module Volt
157
159
  # This will then be passed to the backend to run the query.
158
160
  #
159
161
  # @return [Cursor] a new cursor
160
- def add_query_part(*args)
162
+ def add_query_part(*args, &block)
161
163
  opts = @model.options
162
164
  query = opts[:query] ? opts[:query].deep_clone : []
163
- query << args
165
+
166
+ if args[0] == :where && block
167
+ # Change the query name to show that we were passed a block
168
+ args[0] = :where_with_block
169
+ # Block was passed to where query, pass in the QueryIdentifier
170
+ result = block.call(QueryIdentifier.new)
171
+
172
+ query << [*args, result.to_query]
173
+ else
174
+ query << args
175
+ end
164
176
 
165
177
  # Make a new opts hash with changed query
166
178
  opts = opts.merge(query: query)
167
179
  Cursor.new([], opts)
168
180
  end
169
181
 
182
+ # When doing things like .count, we get back a value, not an Array of
183
+ # Models. To handle this, the updates from the server just resolve the
184
+ # value directly.
185
+ def resolve_value(val)
186
+ if @resolve_promises
187
+ # Resolve all waiting promises
188
+ resolve_promises.each do |promise|
189
+ promise.resolve(val)
190
+ end
191
+ else
192
+ # Only invalidate to generate a new promise if the previous one is
193
+ # already realized.
194
+ resolve_dep.changed!
195
+ @resolve_dep = nil
196
+ end
197
+
198
+ # @root_dep.changed!
199
+
200
+ @model.value = val
201
+ @resolve_promises = nil
202
+ end
203
+
204
+ def value
205
+ # proc do |comp|
206
+ @root_dep.depend
207
+ resolve_dep.depend
208
+ # comp.stop
209
+ # end.watch!
210
+ unless Computation.current
211
+ # Load the data now manually not running inside of a computation.
212
+ load_data
213
+ end
214
+
215
+ if @value.respond_to?(:has_value?) && @model.has_value? && (resval = self.resolved_value)
216
+ # Value is already there, return a resolved promise
217
+ return Promise.new.resolve(resval)
218
+ else
219
+ # Otherwise generate a new promise and queue it for resolving
220
+ promise = Promise.new
221
+ resolve_promises << promise
222
+
223
+ return promise
224
+ end
225
+ end
226
+
227
+ # Return a promise for resolving a value from something like .count
228
+ def resolve_promises
229
+ @resolve_promises ||= []
230
+ end
231
+
232
+
233
+ def resolve_dep
234
+ @resolve_dep ||= Dependency.new
235
+ end
236
+
237
+ def resolved_value
238
+ if @value.respond_to?(:has_value?) && @model.has_value?
239
+ @model.value
240
+ else
241
+ nil
242
+ end
243
+ end
244
+
170
245
  # Call a method on the model once the model is loaded. Return a promise
171
246
  # that will resolve when the model is loaded
172
247
  def run_once_loaded
@@ -191,6 +266,12 @@ module Volt
191
266
  # passed block will be passed to the promises then. Then will be passed the model.
192
267
  def fetch(&block)
193
268
  Volt.logger.warn('Deprication warning: in 0.9.3.pre4, all query methods on store now return Promises, so you can juse use .all or .first instead of .fetch')
269
+ begin
270
+ raise
271
+ rescue => e
272
+ puts e.inspect
273
+ puts e.backtrace.join("\n")
274
+ end
194
275
  promise = Promise.new
195
276
 
196
277
  # Run the block after resolve if a block is passed in
@@ -211,10 +292,6 @@ module Volt
211
292
  promise
212
293
  end
213
294
 
214
- # Alias then for now
215
- # TODO: Deprecate
216
- alias_method :then, :fetch
217
-
218
295
  # Called from backend when an item is added
219
296
  def add(index, data)
220
297
  $loading_models = true
@@ -293,6 +370,8 @@ module Volt
293
370
  def async?
294
371
  true
295
372
  end
373
+
374
+ private
296
375
  end
297
376
  end
298
377
  end
@@ -46,6 +46,25 @@ module Volt
46
46
  false
47
47
  end
48
48
 
49
+ # Stores can allow or not allow "on the fly collections", this means a
50
+ # collection will be created when saved to, even if the collection did
51
+ # not exist previously and was not created with a class.
52
+ #
53
+ # If we allow on the fly collections, we could do the following:
54
+ # store._somethings
55
+ # ^ where no Something model class had been created
56
+ def on_the_fly_collections?
57
+ true
58
+ end
59
+
60
+ # Count gets passed down to the persistor, since different persistors
61
+ # may implement it different ways. #reactive_count just aliases normal
62
+ # count for now.
63
+ def count(*args, &block)
64
+ @model.reactive_count(*args, &block)
65
+ end
66
+
67
+
49
68
  # Find the root for this model
50
69
  def root_model
51
70
  node = @model
@@ -159,7 +159,7 @@ module Volt
159
159
 
160
160
  if RUBY_PLATFORM != 'opal'
161
161
  def db
162
- @@db ||= Volt::DataStore.fetch
162
+ @@db ||= Volt::DataStore.fetch(Volt.current_app)
163
163
  end
164
164
 
165
165
  # Do the actual writing of data to the database, only runs on the backend.
@@ -8,7 +8,7 @@ module Volt
8
8
  end
9
9
 
10
10
  def where(query)
11
- @model.select do |model|
11
+ result = @model.select do |model|
12
12
  # Run through each key in the query and make sure the value matches.
13
13
  # We use .all? because once one fails to match, we can return false,
14
14
  # because it wouldn't match as a whole.
@@ -16,6 +16,9 @@ module Volt
16
16
  model.get(key) == value
17
17
  end
18
18
  end
19
+
20
+ options = @model.options.merge(parent: @model, path: @model.path)
21
+ @model.new_array_model(result, options)
19
22
  end
20
23
  end
21
24
  end
@@ -0,0 +1,102 @@
1
+ # Volt allows you to pass blocks to .where queries. The block will receive a
2
+ # single argument, which sumulates the row in the database. On the row, you
3
+ # can call comparitors and use logical and/or (| and &) to build up queries.
4
+ #
5
+ # The QueryIdentifier class stores all methods called on it, and uses the
6
+ # QueryOp class to store logical operations. You can call .to_query on the
7
+ # result to get an array representing the query. This can then be normalized
8
+ # and called on the backend to answer the query.
9
+
10
+ module Volt
11
+ class QueryIdentifier
12
+ def initialize(from='ident')
13
+ @from = from
14
+ end
15
+
16
+ def method_missing(method_name, *args)
17
+ method_name = method_name.to_s
18
+ # c for call
19
+
20
+ args.map do |arg|
21
+ if arg.is_a?(Array)
22
+ # Escape the array
23
+ arg.unshift('a')
24
+ end
25
+ end
26
+
27
+ QueryIdentifier.new(['c', @from, method_name, *args])
28
+ end
29
+
30
+ def ==(val)
31
+ method_missing(:==, val)
32
+ end
33
+
34
+ # def !
35
+ # method_missing(:!)
36
+ # end
37
+
38
+ # Special methods in ruby don't respond to method_missing, so setup methods
39
+ # for them.
40
+ # We need < and > until https://github.com/opal/opal/issues/1137 is fixed.
41
+ ['=~', '!~', '&', '|', '<', '>', '<=', '>='].each do |op|
42
+ define_method(op) do |val|
43
+ __op(op, val)
44
+ end
45
+ end
46
+
47
+ # And for unary ops
48
+ def ~
49
+ __op('~')
50
+ end
51
+
52
+ def +(val)
53
+ method_missing(:+, val)
54
+ end
55
+
56
+ def __op(*args)
57
+ QueryOp.new(['c', self, *args])
58
+ end
59
+
60
+ def coerce(other)
61
+ unless other.is_a?(Volt::QueryIdentifier)
62
+ other = Volt::QueryIdentifier.new(other)
63
+ end
64
+
65
+ [other, self]
66
+ end
67
+
68
+ def inspect
69
+ "(#{@from.join(' ')})"
70
+ end
71
+
72
+ def to_s
73
+ inspect
74
+ end
75
+
76
+ def to_query
77
+ if @from.is_a?(Array)
78
+ @from.map do |val|
79
+ if val.is_a?(QueryIdentifier)
80
+ val.to_query
81
+ else
82
+ val
83
+ end
84
+ end
85
+ else
86
+ @from
87
+ end
88
+ end
89
+ end
90
+
91
+ class QueryOp < QueryIdentifier
92
+
93
+ end
94
+ end
95
+
96
+ # def where
97
+ # ident = Volt::QueryIdentifier.new
98
+ # yield(ident)
99
+ # end
100
+
101
+ # a = where {|v| ((v.name < 10) | (v.name > 5)) & ~(v.name == 'Ryan') }
102
+ # b = where {|v| 1 < v.name }
@@ -1,5 +1,5 @@
1
1
  module Volt
2
- # The query listener is what gets notified on the backend when the results from
2
+ # The query listener is what gets notified from the backend when the results from
3
3
  # a query have changed. It then will make the necessary changes to any ArrayStore's
4
4
  # to get them to display the new data.
5
5
  class QueryListener
@@ -12,6 +12,7 @@ module Volt
12
12
 
13
13
  @collection = collection
14
14
  @query = query
15
+ @data = []
15
16
 
16
17
  @listening = false
17
18
  end
@@ -23,6 +24,9 @@ module Volt
23
24
  QueryTasks.add_listener(@collection, @query).then do |ret|
24
25
  results, errors = ret
25
26
 
27
+ # Store the data
28
+ @data = results
29
+
26
30
  # When the initial data comes back, add it into the stores.
27
31
  @stores.dup.each do |store|
28
32
  # Clear if there are existing items
@@ -30,11 +34,15 @@ module Volt
30
34
  store.model.clear if store.model.size > 0
31
35
  end
32
36
 
33
- results.each do |index, data|
34
- store.add(index, data)
35
- end
37
+ if results.is_a?(Array)
38
+ results.each_with_index do |data, index|
39
+ store.add(index, data)
40
+ end
36
41
 
37
- store.model.change_state_to(:loaded_state, :loaded)
42
+ store.model.change_state_to(:loaded_state, :loaded)
43
+ else
44
+ store.resolve_value(results)
45
+ end
38
46
 
39
47
  # if Volt.server?
40
48
  # store.model.change_state_to(:loaded_state, :dirty)
@@ -42,7 +50,7 @@ module Volt
42
50
  end
43
51
  end.fail do |err|
44
52
  # TODO: need to make it so we can re-raise out of this promise
45
- msg = "Error adding listener: #{err.inspect}"
53
+ msg = "Database Query Error: #{err.inspect}"
46
54
  msg += "\n#{err.backtrace.join("\n")}" if err.respond_to?(:backtrace)
47
55
  Volt.logger.error(msg)
48
56
 
@@ -64,13 +72,20 @@ module Volt
64
72
  # copy the data from the existing model.
65
73
  store.model.clear
66
74
 
67
- # Get an existing store to copy data from
68
- first_store_model = @stores.first.model
69
- first_store_model.each_with_index do |item, index|
70
- store.add(index, item.to_h)
75
+ first_store = @stores.first
76
+
77
+ if (resolved_value = first_store.resolved_value)
78
+ store.resolve_value(resolved_value)
79
+ else
80
+ # Get an existing store to copy data from
81
+ first_store_model = first_store.model
82
+ first_store_model.each_with_index do |item, index|
83
+ store.add(index, item.to_h)
84
+ end
85
+
86
+ store.model.change_state_to(:loaded_state, first_store_model.loaded_state)
71
87
  end
72
88
 
73
- store.model.change_state_to(:loaded_state, first_store_model.loaded_state)
74
89
  else
75
90
  # First time we've added a store, setup the listener and get
76
91
  # the initial data.
@@ -94,7 +109,30 @@ module Volt
94
109
  end
95
110
  end
96
111
 
97
- def added(index, data)
112
+ def updated(diff)
113
+ diff.each do |op|
114
+ operation, arg1, arg2 = op
115
+
116
+ case operation
117
+ when 'i'
118
+ # insert
119
+ inserted(arg1, arg2)
120
+ when 'r'
121
+ # remove
122
+ removed([arg1])
123
+ when 'c'
124
+ # changed
125
+ changed(arg1, arg2)
126
+ when 'm'
127
+ # move
128
+ when 'u'
129
+ # single value, update it
130
+ update(arg1)
131
+ end
132
+ end
133
+ end
134
+
135
+ def inserted(index, data)
98
136
  @stores.each do |store|
99
137
  store.add(index, data)
100
138
  end
@@ -106,7 +144,14 @@ module Volt
106
144
  end
107
145
  end
108
146
 
147
+ def update(data)
148
+ @stores.each do |store|
149
+ store.resolve_value(data)
150
+ end
151
+ end
152
+
109
153
  def changed(model_id, data)
154
+ puts "CH: #{model_id.inspect} - #{data.inspect}"
110
155
  $loading_models = true
111
156
  Persistors::ModelStore.changed(model_id, data)
112
157
  $loading_models = false