volt 0.9.2 → 0.9.3.pre1

Sign up to get free protection for your applications and to get access to all the features.
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