volt 0.9.2 → 0.9.3.pre1

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/CONTRIBUTING.md +4 -0
  4. data/Gemfile +3 -0
  5. data/app/volt/assets/js/volt_js_polyfills.js +0 -1
  6. data/app/volt/assets/js/volt_watch.js +217 -0
  7. data/app/volt/models/user.rb +7 -2
  8. data/app/volt/tasks/query_tasks.rb +6 -0
  9. data/lib/volt/boot.rb +1 -1
  10. data/lib/volt/cli/generate.rb +1 -1
  11. data/lib/volt/config.rb +12 -2
  12. data/lib/volt/controllers/model_controller.rb +1 -1
  13. data/lib/volt/data_stores/base_adaptor_client.rb +34 -0
  14. data/lib/volt/data_stores/{base.rb → base_adaptor_server.rb} +1 -1
  15. data/lib/volt/data_stores/data_store.rb +23 -7
  16. data/lib/volt/models/array_model.rb +3 -2
  17. data/lib/volt/models/model.rb +29 -91
  18. data/lib/volt/models/{dirty.rb → model_helpers/dirty.rb} +0 -0
  19. data/lib/volt/models/{listener_tracker.rb → model_helpers/listener_tracker.rb} +0 -0
  20. data/lib/volt/models/model_helpers/model_change_helpers.rb +76 -0
  21. data/lib/volt/models/{model_helpers.rb → model_helpers/model_helpers.rb} +0 -0
  22. data/lib/volt/models/persistors/array_store.rb +2 -23
  23. data/lib/volt/models/persistors/query/normalizer.rb +0 -44
  24. data/{templates/project/lib/.empty_directory → lib/volt/models/validations/errors.rb} +0 -0
  25. data/lib/volt/models/{validations.rb → validations/validations.rb} +80 -26
  26. data/lib/volt/page/bindings/attribute_binding.rb +17 -3
  27. data/lib/volt/page/page.rb +1 -0
  28. data/lib/volt/reactive/eventable.rb +1 -0
  29. data/lib/volt/server.rb +2 -1
  30. data/lib/volt/server/component_templates.rb +66 -16
  31. data/lib/volt/server/forking_server.rb +16 -14
  32. data/lib/volt/server/html_parser/sandlebars_parser.rb +2 -0
  33. data/lib/volt/server/html_parser/view_scope.rb +2 -0
  34. data/lib/volt/server/rack/component_paths.rb +4 -2
  35. data/lib/volt/server/rack/opal_files.rb +4 -2
  36. data/lib/volt/server/socket_connection_handler.rb +5 -1
  37. data/lib/volt/server/template_handlers/handlers.rb +0 -0
  38. data/lib/volt/spec/setup.rb +23 -8
  39. data/lib/volt/tasks/dispatcher.rb +4 -0
  40. data/lib/volt/utils/promise_patch.rb +3 -0
  41. data/lib/volt/version.rb +1 -1
  42. data/spec/apps/kitchen_sink/Gemfile +5 -0
  43. data/spec/apps/kitchen_sink/app/main/config/routes.rb +1 -0
  44. data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +10 -0
  45. data/spec/apps/kitchen_sink/app/main/views/main/bindings.html +20 -0
  46. data/spec/apps/kitchen_sink/app/main/views/main/form.html +73 -0
  47. data/spec/apps/kitchen_sink/app/main/views/main/main.html +1 -0
  48. data/spec/integration/bindings_spec.rb +33 -0
  49. data/spec/integration/user_spec.rb +51 -21
  50. data/spec/models/associations_spec.rb +8 -0
  51. data/spec/models/model_spec.rb +7 -0
  52. data/spec/models/user_spec.rb +20 -0
  53. data/spec/models/validations_spec.rb +2 -1
  54. data/spec/models/validators/block_validations_spec.rb +53 -0
  55. data/spec/page/bindings/template_binding/view_lookup_for_path_spec.rb +10 -0
  56. data/spec/page/path_string_renderer_spec.rb +6 -0
  57. data/spec/reactive/eventable_spec.rb +24 -6
  58. data/spec/server/component_templates_spec.rb +21 -0
  59. data/spec/server/html_parser/sandlebars_parser_spec.rb +12 -13
  60. data/spec/server/html_parser/view_parser_spec.rb +3 -0
  61. data/spec/server/rack/asset_files_spec.rb +2 -2
  62. data/spec/server/rack/http_resource_spec.rb +10 -0
  63. data/spec/tasks/dispatcher_spec.rb +5 -0
  64. data/spec/tasks/user_tasks_spec.rb +59 -0
  65. data/spec/utils/task_argument_filtererer_spec.rb +6 -0
  66. data/templates/newgem/app/newgem/config/initializers/boot.rb +10 -0
  67. data/templates/newgem/lib/newgem.rb.tt +13 -0
  68. data/templates/project/Gemfile.tt +3 -0
  69. data/templates/project/app/main/lib/.empty_directory +0 -0
  70. data/volt.gemspec +3 -1
  71. metadata +24 -25
  72. data/lib/volt/data_stores/mongo_driver.rb +0 -69
@@ -1,8 +1,9 @@
1
1
  require 'volt/reactive/reactive_array'
2
2
  require 'volt/models/model_wrapper'
3
- require 'volt/models/model_helpers'
3
+ require 'volt/models/model_helpers/model_helpers'
4
4
  require 'volt/models/state_manager'
5
5
  require 'volt/models/state_helpers'
6
+ require 'volt/data_stores/data_store'
6
7
 
7
8
  module Volt
8
9
  class ArrayModel < ReactiveArray
@@ -40,7 +41,7 @@ module Volt
40
41
  end
41
42
 
42
43
  proxy_with_root_dep :[], :size, :first, :last, :state_for, :reverse
43
- proxy_to_persistor :find, :where, :skip, :sort, :limit, :then, :fetch, :fetch_first, :fetch_each
44
+ proxy_to_persistor :then, :fetch, :fetch_first, :fetch_each
44
45
 
45
46
  def initialize(array = [], options = {})
46
47
  @options = options
@@ -1,8 +1,8 @@
1
1
  require 'volt/models/model_wrapper'
2
2
  require 'volt/models/array_model'
3
- require 'volt/models/model_helpers'
3
+ require 'volt/models/model_helpers/model_helpers'
4
4
  require 'volt/models/model_hash_behaviour'
5
- require 'volt/models/validations'
5
+ require 'volt/models/validations/validations'
6
6
  require 'volt/utils/modes'
7
7
  require 'volt/models/state_manager'
8
8
  require 'volt/models/state_helpers'
@@ -10,8 +10,9 @@ require 'volt/models/buffer'
10
10
  require 'volt/models/field_helpers'
11
11
  require 'volt/reactive/reactive_hash'
12
12
  require 'volt/models/validators/user_validation'
13
- require 'volt/models/dirty'
14
- require 'volt/models/listener_tracker'
13
+ require 'volt/models/model_helpers/dirty'
14
+ require 'volt/models/model_helpers/listener_tracker'
15
+ require 'volt/models/model_helpers/model_change_helpers'
15
16
  require 'volt/models/permissions'
16
17
  require 'volt/models/associations'
17
18
  require 'volt/reactive/class_eventable'
@@ -45,6 +46,7 @@ module Volt
45
46
  include Permissions
46
47
  include Associations
47
48
  include ReactiveAccessors
49
+ include ModelChangeHelpers
48
50
 
49
51
  attr_reader :attributes, :parent, :path, :persistor, :options
50
52
 
@@ -149,24 +151,7 @@ module Volt
149
151
  @deps.changed_all!
150
152
  @deps = HashDependency.new
151
153
 
152
- # Save the changes
153
- if initial_setup
154
- # Run initial validation
155
- if Volt.in_mode?(:no_validate)
156
- # No validate, resolve nil
157
- Promise.new.resolve(nil)
158
- else
159
- return validate!.then do |errs|
160
- if errs && errs.size > 0
161
- Promise.new.reject(errs)
162
- else
163
- Promise.new.resolve(nil)
164
- end
165
- end
166
- end
167
- else
168
- return run_changed
169
- end
154
+ run_initial_setup(initial_setup)
170
155
  end
171
156
 
172
157
  alias_method :attributes=, :assign_attributes
@@ -353,6 +338,28 @@ module Volt
353
338
  end
354
339
 
355
340
  private
341
+ def run_initial_setup(initial_setup)
342
+
343
+ # Save the changes
344
+ if initial_setup
345
+ # Run initial validation
346
+ if Volt.in_mode?(:no_validate)
347
+ # No validate, resolve nil
348
+ Promise.new.resolve(nil)
349
+ else
350
+ return validate!.then do |errs|
351
+ if errs && errs.size > 0
352
+ Promise.new.reject(errs)
353
+ else
354
+ Promise.new.resolve(nil)
355
+ end
356
+ end
357
+ end
358
+ else
359
+ return run_changed
360
+ end
361
+ end
362
+
356
363
 
357
364
  # Volt provides a few access methods to get more data about the model,
358
365
  # we want to prevent these from being assigned or accessed through
@@ -363,17 +370,6 @@ module Volt
363
370
  end
364
371
  end
365
372
 
366
- def setup_buffer(model)
367
- Volt::Model.no_validate do
368
- model.assign_attributes(attributes, true)
369
- end
370
-
371
- model.change_state_to(:loaded_state, :loaded)
372
-
373
- # Set new to the same as the main model the buffer is from
374
- model.instance_variable_set('@new', @new)
375
- end
376
-
377
373
  # Takes the persistor if there is one and
378
374
  def setup_persistor(persistor)
379
375
  @persistor = persistor.new(self) if persistor
@@ -398,63 +394,5 @@ module Volt
398
394
  end
399
395
  end
400
396
  end
401
-
402
- # Called when something in the model changes. Saves
403
- # the model if there is a persistor, and changes the
404
- # model to not be new.
405
- #
406
- # @return [Promise|nil] a promise for when the save is
407
- # complete
408
- def run_changed(attribute_name = nil)
409
- # no_validate mode should only be used internally. no_validate mode is a
410
- # performance optimization that prevents validation from running after each
411
- # change when assigning multile attributes.
412
- unless Volt.in_mode?(:no_validate)
413
- # Run the validations for all fields
414
- result = nil
415
- return validate!.then do
416
- # Buffers are allowed to be in an invalid state
417
- unless buffer?
418
- # First check that all local validations pass
419
- if error_in_changed_attributes?
420
- # Some errors are present, revert changes
421
- revert_changes!
422
-
423
- # After we revert, we need to validate again to get the error messages back
424
- # TODO: Could probably cache the previous errors.
425
- result = validate!.then do
426
- # Reject the promise with the errors
427
- Promise.new.reject(errs)
428
- end
429
- else
430
- # No errors, tell the persistor to handle the change (usually save)
431
-
432
- # Don't save right now if we're in a nosave block
433
- unless Volt.in_mode?(:no_save)
434
- # the changed method on a persistor should return a promise that will
435
- # be resolved when the save is complete, or fail with a hash of errors.
436
- if @persistor
437
- result = @persistor.changed(attribute_name)
438
- else
439
- result = Promise.new.resolve(nil)
440
- end
441
-
442
- # Saved, no longer new
443
- @new = false
444
-
445
- # Clear the change tracking
446
- clear_tracked_changes!
447
- end
448
- end
449
- end
450
-
451
- # Return result inside of the validate! promise
452
- result
453
- end
454
- end
455
-
456
- # Didn't run validations
457
- nil
458
- end
459
397
  end
460
398
  end
@@ -0,0 +1,76 @@
1
+ # ModelChangeHelpers handle validating and persisting the data in a model
2
+ # when it is changed. #run_changed will be called from the model.
3
+
4
+ module Volt
5
+ module ModelChangeHelpers
6
+ private
7
+
8
+ # Called when something in the model changes. Saves
9
+ # the model if there is a persistor, and changes the
10
+ # model to not be new.
11
+ #
12
+ # @return [Promise|nil] a promise for when the save is
13
+ # complete
14
+ def run_changed(attribute_name = nil)
15
+ # no_validate mode should only be used internally. no_validate mode is a
16
+ # performance optimization that prevents validation from running after each
17
+ # change when assigning multile attributes.
18
+ unless Volt.in_mode?(:no_validate)
19
+ # Run the validations for all fields
20
+ result = nil
21
+ return validate!.then do
22
+ # Buffers are allowed to be in an invalid state
23
+ unless buffer?
24
+ # First check that all local validations pass
25
+ if error_in_changed_attributes?
26
+ # Some errors are present, revert changes
27
+ revert_changes!
28
+
29
+ # After we revert, we need to validate again to get the error messages back
30
+ # TODO: Could probably cache the previous errors.
31
+ result = validate!.then do
32
+ # Reject the promise with the errors
33
+ Promise.new.reject(errs)
34
+ end
35
+ else
36
+ result = persist_changes(attribute_name)
37
+ end
38
+ end
39
+
40
+ # Return result inside of the validate! promise
41
+ result
42
+ end
43
+ end
44
+
45
+ # Didn't run validations
46
+ nil
47
+ end
48
+
49
+
50
+ # Should only be called from run_changed. Saves the changes back to the persistor
51
+ # and clears the tracked changes.
52
+ def persist_changes(attribute_name)
53
+ # No errors, tell the persistor to handle the change (usually save)
54
+
55
+ # Don't save right now if we're in a nosave block
56
+ unless Volt.in_mode?(:no_save)
57
+ # the changed method on a persistor should return a promise that will
58
+ # be resolved when the save is complete, or fail with a hash of errors.
59
+ if @persistor
60
+ result = @persistor.changed(attribute_name)
61
+ else
62
+ result = Promise.new.resolve(nil)
63
+ end
64
+
65
+ # Saved, no longer new
66
+ @new = false
67
+
68
+ # Clear the change tracking
69
+ clear_tracked_changes!
70
+ end
71
+
72
+ result
73
+ end
74
+
75
+ end
76
+ end
@@ -1,6 +1,5 @@
1
1
  require 'volt/models/persistors/store'
2
2
  require 'volt/models/persistors/store_state'
3
- require 'volt/models/persistors/query/normalizer'
4
3
  require 'volt/models/persistors/query/query_listener_pool'
5
4
  require 'volt/utils/timers'
6
5
 
@@ -9,6 +8,7 @@ module Volt
9
8
  class ArrayStore < Store
10
9
  include StoreState
11
10
 
11
+
12
12
  @@query_pool = QueryListenerPool.new
13
13
 
14
14
  attr_reader :model, :root_dep
@@ -141,7 +141,7 @@ module Volt
141
141
  end
142
142
  end
143
143
 
144
- query = Query::Normalizer.normalize(query)
144
+ query = Volt::DataStore.adaptor_client.normalize_query(query)
145
145
 
146
146
  @query_listener ||= @@query_pool.lookup(collection, query) do
147
147
  # Create if it does not exist
@@ -153,27 +153,6 @@ module Volt
153
153
  @query_listener
154
154
  end
155
155
 
156
- # Find takes a query object
157
- def where(query = nil)
158
- query ||= {}
159
-
160
- add_query_part(:find, query)
161
- end
162
- alias_method :find, :where
163
-
164
- def limit(limit)
165
- add_query_part(:limit, limit)
166
- end
167
-
168
- def skip(skip)
169
- add_query_part(:skip, skip)
170
- end
171
-
172
- # .sort is already a ruby method, so we use order instead
173
- def order(sort)
174
- add_query_part(:sort, sort)
175
- end
176
-
177
156
  # Add query part adds a [method_name, *arguments] array to the query.
178
157
  # This will then be passed to the backend to run the query.
179
158
  #
@@ -2,50 +2,6 @@ module Volt
2
2
  module Query
3
3
  # Normalizes queries so queries that are the same have the same order and parts
4
4
  class Normalizer
5
- def self.normalize(query)
6
- query = merge_finds_and_move_to_front(query)
7
-
8
- query = reject_skip_zero(query)
9
-
10
- query
11
- end
12
-
13
- def self.merge_finds_and_move_to_front(query)
14
- # Map first parts to string
15
- query = query.map { |v| v[0] = v[0].to_s; v }
16
- has_find = query.find { |v| v[0] == 'find' }
17
-
18
- if has_find
19
- # merge any finds
20
- merged_find_query = {}
21
- query = query.reject do |query_part|
22
- if query_part[0] == 'find'
23
- # on a find, merge into finds
24
- find_query = query_part[1]
25
- merged_find_query.merge!(find_query) if find_query
26
-
27
- # reject
28
- true
29
- else
30
- false
31
- end
32
- end
33
-
34
- # Add finds to the front
35
- query.insert(0, ['find', merged_find_query])
36
- else
37
- # No find was done, add it in the first position
38
- query.insert(0, ['find'])
39
- end
40
-
41
- query
42
- end
43
-
44
- def self.reject_skip_zero(query)
45
- query.reject do |query_part|
46
- query_part[0] == 'skip' && query_part[1] == 0
47
- end
48
- end
49
5
  end
50
6
  end
51
7
  end
@@ -11,6 +11,8 @@ module Volt
11
11
  # Include in any class to get validation logic
12
12
  module Validations
13
13
  module ClassMethods
14
+ # Validate is called directly on the class and sets up the validation to be run
15
+ # each time validate! is called on the class.
14
16
  def validate(field_name = nil, options = nil, &block)
15
17
  if block
16
18
  if field_name || options
@@ -19,16 +21,47 @@ module Volt
19
21
  self.custom_validations ||= []
20
22
  custom_validations << block
21
23
  else
22
- self.validations ||= {}
23
- validations[field_name] ||= {}
24
- validations[field_name].merge!(options)
24
+ self.validations_to_run ||= {}
25
+ validations_to_run[field_name] ||= {}
26
+ validations_to_run[field_name].merge!(options)
25
27
  end
26
28
  end
29
+
30
+ # Validations takes a block, and can contain validate calls inside of it
31
+ # which will conditionally be run based on the code in the block. The
32
+ # context of the block will be the current model.
33
+ def validations(*run_in_actions, &block)
34
+ unless block_given?
35
+ raise 'validations must take a block, use `validate` to setup a validation on a class directly.'
36
+ end
37
+
38
+ # Add a validation block to run during each validation
39
+ validate do
40
+ action = new? ? :create : :update
41
+
42
+ if run_in_actions.size == 0 || run_in_actions.include?(action)
43
+ @instance_validations = {}
44
+
45
+ instance_exec(action, &block)
46
+
47
+ result = run_validations(@instance_validations)
48
+
49
+ @instance_validations = nil
50
+
51
+ result
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ def validate(field_name = nil, options = nil)
58
+ @instance_validations[field_name] ||= {}
59
+ @instance_validations[field_name].merge!(options)
27
60
  end
28
61
 
29
62
  def self.included(base)
30
63
  base.send :extend, ClassMethods
31
- base.class_attribute(:custom_validations, :validations)
64
+ base.class_attribute(:custom_validations, :validations_to_run)
32
65
  end
33
66
 
34
67
  # Once a field is ready, we can use include_in_errors! to start
@@ -43,11 +76,21 @@ module Volt
43
76
 
44
77
  # Marks all fields, useful for when a model saves.
45
78
  def mark_all_fields!
46
- validations = self.class.validations
79
+ # TODO: We can use a Set here, but set was having issues. Check in a
80
+ # later version of opal.
81
+ fields_to_mark = []
82
+
83
+ # Look at each validation
84
+ validations = self.class.validations_to_run
47
85
  if validations
48
- validations.each_key do |key|
49
- mark_field!(key.to_sym)
50
- end
86
+ fields_to_mark += validations.keys
87
+ end
88
+
89
+ # Also include any current fields
90
+ fields_to_mark += attributes.keys
91
+
92
+ fields_to_mark.each do |key|
93
+ mark_field!(key.to_sym)
51
94
  end
52
95
  end
53
96
 
@@ -110,31 +153,42 @@ module Volt
110
153
  private
111
154
 
112
155
  # Runs through each of the normal validations.
156
+ # @param [Array] An array of validations to run
113
157
  # @return [Promise] a promsie to run all validations
114
- def run_validations
115
- promise = Promise.new.resolve(nil)
158
+ def run_validations(validations = nil)
159
+ # Default to running the class level validations
160
+ validations ||= self.class.validations_to_run
116
161
 
117
- validations = self.class.validations
162
+ promise = Promise.new.resolve(nil)
118
163
  if validations
119
164
 
120
165
  # Run through each validation
121
166
  validations.each_pair do |field_name, options|
122
- options.each_pair do |validation, args|
123
- # Call the specific validator, then merge the results back
124
- # into one large errors hash.
125
- klass = validation_class(validation, args)
126
-
127
- if klass
128
- # Chain on the promises
129
- promise = promise.then do
130
- klass.validate(self, field_name, args)
131
- end.then do |errs|
132
- errors.merge!(errs)
133
- end
134
- else
135
- fail "validation type #{validation} is not specified."
136
- end
167
+ promise = promise.then { run_validation(field_name, options) }
168
+ end
169
+ end
170
+
171
+ promise
172
+ end
173
+
174
+ # Runs an individual validation
175
+ # @returns [Promise]
176
+ def run_validation(field_name, options)
177
+ promise = Promise.new.resolve(nil)
178
+ options.each_pair do |validation, args|
179
+ # Call the specific validator, then merge the results back
180
+ # into one large errors hash.
181
+ klass = validation_class(validation, args)
182
+
183
+ if klass
184
+ # Chain on the promises
185
+ promise = promise.then do
186
+ klass.validate(self, field_name, args)
187
+ end.then do |errs|
188
+ errors.merge!(errs)
137
189
  end
190
+ else
191
+ fail "validation type #{validation} is not specified."
138
192
  end
139
193
  end
140
194