volt 0.9.3.pre2 → 0.9.3.pre3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/app/volt/tasks/query_tasks.rb +0 -7
  4. data/app/volt/tasks/store_tasks.rb +0 -6
  5. data/docs/UPGRADE_GUIDE.md +2 -0
  6. data/lib/volt/cli/asset_compile.rb +2 -2
  7. data/lib/volt/cli/console.rb +21 -0
  8. data/lib/volt/config.rb +0 -10
  9. data/lib/volt/controllers/collection_helpers.rb +18 -0
  10. data/lib/volt/controllers/model_controller.rb +2 -12
  11. data/lib/volt/extra_core/object.rb +19 -0
  12. data/lib/volt/models.rb +14 -9
  13. data/lib/volt/models/array_model.rb +62 -22
  14. data/lib/volt/models/associations.rb +16 -1
  15. data/lib/volt/models/model.rb +27 -15
  16. data/lib/volt/models/model_helpers/model_helpers.rb +29 -0
  17. data/lib/volt/models/permissions.rb +15 -4
  18. data/lib/volt/models/persistors/array_store.rb +40 -0
  19. data/lib/volt/models/persistors/model_store.rb +2 -2
  20. data/lib/volt/models/persistors/query/query_listener.rb +3 -1
  21. data/lib/volt/models/persistors/store.rb +2 -1
  22. data/lib/volt/models/root_models/root_models.rb +31 -0
  23. data/lib/volt/models/root_models/store_root.rb +36 -0
  24. data/lib/volt/models/validators/unique_validator.rb +1 -1
  25. data/lib/volt/page/bindings/each_binding.rb +56 -47
  26. data/lib/volt/page/page.rb +5 -5
  27. data/lib/volt/reactive/reactive_array.rb +9 -6
  28. data/lib/volt/server.rb +2 -2
  29. data/lib/volt/server/component_templates.rb +7 -4
  30. data/lib/volt/server/message_bus/message_encoder.rb +9 -1
  31. data/lib/volt/server/rack/component_code.rb +8 -1
  32. data/lib/volt/server/rack/index_files.rb +5 -2
  33. data/lib/volt/tasks/{task_handler.rb → task.rb} +6 -6
  34. data/lib/volt/utils/promise.rb +429 -0
  35. data/lib/volt/utils/promise_extensions.rb +79 -0
  36. data/lib/volt/version.rb +1 -1
  37. data/lib/volt/volt/app.rb +5 -2
  38. data/lib/volt/volt/server_setup/app.rb +28 -7
  39. data/spec/apps/kitchen_sink/app/main/controllers/server/simple_http_controller.rb +1 -1
  40. data/spec/apps/kitchen_sink/app/main/views/main/store.html +3 -0
  41. data/spec/extra_core/object_spec.rb +13 -0
  42. data/spec/integration/store_spec.rb +10 -0
  43. data/spec/models/associations_spec.rb +48 -26
  44. data/spec/models/model_spec.rb +23 -7
  45. data/spec/models/persistors/store_spec.rb +28 -0
  46. data/spec/models/validators/unique_validator_spec.rb +1 -1
  47. data/spec/spec_helper.rb +4 -1
  48. data/spec/utils/promise_extensions_spec.rb +42 -0
  49. data/templates/component/config/initializers/boot.rb +10 -0
  50. data/templates/{project/app → component/config/initializers/client}/.empty_directory +0 -0
  51. data/templates/component/config/initializers/server/.empty_directory +0 -0
  52. data/templates/newgem/app/newgem/config/initializers/client/.empty_directory +0 -0
  53. data/templates/newgem/app/newgem/config/initializers/server/.empty_directory +0 -0
  54. data/templates/project/Gemfile.tt +6 -2
  55. data/templates/project/app/main/config/initializers/boot.rb +10 -0
  56. data/templates/project/app/main/config/initializers/client/.empty_directory +0 -0
  57. data/templates/project/app/main/config/initializers/server/.empty_directory +0 -0
  58. data/templates/project/config/app.rb.tt +3 -0
  59. data/templates/project/config/initializers/client/.empty_directory +0 -0
  60. data/templates/project/config/initializers/server/.empty_directory +0 -0
  61. metadata +22 -5
  62. data/lib/volt/utils/promise_patch.rb +0 -70
@@ -32,6 +32,35 @@ module Volt
32
32
  end
33
33
 
34
34
 
35
+ # Return the attributes that are only for this model and any hash sub models
36
+ # but not any sub-associations.
37
+ def self_attributes
38
+ # Don't store any sub-models, those will do their own saving.
39
+ attributes.reject { |k, v| v.is_a?(ArrayModel) }.map do |k,v|
40
+ if v.is_a?(Model)
41
+ v = v.self_attributes
42
+ end
43
+
44
+ [k,v]
45
+ end.to_h
46
+ end
47
+
48
+
49
+ # Takes the persistor if there is one and
50
+ def setup_persistor(persistor)
51
+ # Use page as the default persistor
52
+ persistor ||= Persistors::Page
53
+ if persistor.respond_to?(:new)
54
+ @persistor = persistor.new(self)
55
+ else
56
+ # an already initialized persistor was passed in
57
+ @persistor = persistor
58
+ end
59
+ end
60
+
61
+
62
+
63
+
35
64
  module ClassMethods
36
65
  # Gets the class for a model at the specified path.
37
66
  def class_at_path(path)
@@ -151,22 +151,33 @@ module Volt
151
151
  # Run the read permission check
152
152
  allow, deny = allow_and_deny_fields(:read)
153
153
 
154
+ result = nil
155
+
154
156
  if allow && allow != true && allow.size > 0
155
157
  # always keep id
156
158
  allow << :id
157
159
 
158
160
  # Only keep fields in the allow list
159
- return @attributes.select { |key| allow.include?(key) }
161
+ result = @attributes.select { |key| allow.include?(key) }
160
162
  elsif deny == true
161
163
  # Only keep id
162
164
  # TODO: Should this be a full reject?
163
- return @attributes.reject { |key| key != :id }
165
+ result = @attributes.reject { |key| key != :id }
164
166
  elsif deny && deny.size > 0
165
167
  # Reject any in the deny list
166
- return @attributes.reject { |key| deny.include?(key) }
168
+ result = @attributes.reject { |key| deny.include?(key) }
167
169
  else
168
- return @attributes
170
+ result = @attributes
169
171
  end
172
+
173
+ # Deeply filter any nested models
174
+ return result.map do |key, value|
175
+ if value.is_a?(Model)
176
+ value = value.filtered_attributes
177
+ end
178
+
179
+ [key, value]
180
+ end.to_h
170
181
  end
171
182
 
172
183
  private
@@ -167,6 +167,46 @@ module Volt
167
167
  Cursor.new([], opts)
168
168
  end
169
169
 
170
+ # Call a method on the model once the model is loaded. Return a promise
171
+ # that will resolve the result of the method, or reject with any
172
+ # Exceptions.
173
+ def run_once_loaded(block)
174
+ promise = Promise.new
175
+
176
+ # call once the method is loaded.
177
+ model_loaded = proc do
178
+ begin
179
+ result = yield
180
+ promise.resolve(result)
181
+ rescue Exception => error
182
+ promise.reject(error)
183
+ end
184
+ end
185
+
186
+ # Run the block after resolve if a block is passed in
187
+ if block
188
+ promise2 = promise.then do |val|
189
+ block.call(val)
190
+ end
191
+ else
192
+ promise2 = promise
193
+ end
194
+
195
+ if @model.loaded_state == :loaded
196
+ model_loaded.call
197
+ else
198
+ proc do |comp|
199
+ if @model.loaded_state == :loaded
200
+ model_loaded.call
201
+
202
+ comp.stop
203
+ end
204
+ end.watch!
205
+ end
206
+
207
+ promise2
208
+ end
209
+
170
210
  # Returns a promise that is resolved/rejected when the query is complete. Any
171
211
  # passed block will be passed to the promises then. Then will be passed the model.
172
212
  def fetch(&block)
@@ -155,8 +155,7 @@ module Volt
155
155
 
156
156
  # Return the attributes that are only for this store, not any sub-associations.
157
157
  def self_attributes
158
- # Don't store any sub-stores, those will do their own saving.
159
- @model.attributes.reject { |k, v| v.is_a?(Model) || v.is_a?(ArrayModel) }
158
+ @model.self_attributes
160
159
  end
161
160
 
162
161
  def collection
@@ -170,6 +169,7 @@ module Volt
170
169
 
171
170
  # Do the actual writing of data to the database, only runs on the backend.
172
171
  def save_to_db!(values)
172
+ # puts "SAVE TO DB: #{values.inspect}"
173
173
  # Check to make sure the model has no validation errors.
174
174
  errors = @model.errors
175
175
  return errors if errors.present?
@@ -26,7 +26,9 @@ module Volt
26
26
  # When the initial data comes back, add it into the stores.
27
27
  @stores.dup.each do |store|
28
28
  # Clear if there are existing items
29
- store.model.clear if store.model.size > 0
29
+ Volt.run_in_mode(:no_model_promises) do
30
+ store.model.clear if store.model.size > 0
31
+ end
30
32
 
31
33
  results.each do |index, data|
32
34
  store.add(index, data)
@@ -26,7 +26,8 @@ module Volt
26
26
  if method_name.plural?
27
27
  model = @model.new_array_model([], options)
28
28
  else
29
- model = @model.new_model(nil, options)
29
+ options[:persistor] = @model.persistor
30
+ model= @model.new_model(nil, options)
30
31
 
31
32
  # TODO: Might not need to assign this
32
33
  @model.attributes ||= {}
@@ -0,0 +1,31 @@
1
+ # When you get the root of a collection (.store, .page, etc...), it gives you
2
+ # back a unique class depending on the collection. This allows you to add
3
+ # things to the root easily.
4
+ #
5
+ # The name of the model will be {CollectionName}Root. All root model classes
6
+ # inherit from BaseRootModel, which provides methods to access the model's
7
+ # from the root. (store.items if you have an Item model for example)
8
+
9
+ # Create a model that is above all of the root models.
10
+ class BaseRootModel < Volt::Model
11
+ end
12
+
13
+
14
+ ROOT_MODEL_NAMES = [:Store, :Page, :Params, :Cookies, :LocalStore, :Flash]
15
+
16
+ ROOT_MODEL_NAMES.each do |base_name|
17
+ Object.const_set("#{base_name}Root", Class.new(BaseRootModel))
18
+ end
19
+
20
+ module Volt
21
+ class RootModels
22
+ def self.add_model_class(klass)
23
+ method_name = klass.to_s.underscore.pluralize
24
+
25
+ # Create a getter for each model class off of root.
26
+ BaseRootModel.send(:define_method, method_name) do
27
+ get(method_name)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,36 @@
1
+ # StoreRoot should already be setup when this class is already loaded. It is a
2
+ # Volt::Model that is loaded as the base class for the root of store.
3
+ #
4
+ # In order to support setting properties directly on store, we create a table
5
+ # called "root_store_models", and create a single
6
+
7
+ require 'volt/models/root_models/root_models'
8
+
9
+ module Volt
10
+ module StoreRootHelpers
11
+ def model_for_root
12
+ root = get(:root_store_models).first_or_create
13
+
14
+ root
15
+ end
16
+
17
+
18
+ def get(attr_name, expand = false)
19
+ if attr_name.singular? && attr_name.to_sym != :id
20
+ model_for_root.get(attr_name, expand)
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ def set(attr_name, value, &block)
27
+ if attr_name.singular? && attr_name.to_sym != :id
28
+ model_for_root.set(attr_name, value, &block)
29
+ else
30
+ super
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ StoreRoot.send(:include, Volt::StoreRootHelpers)
@@ -12,7 +12,7 @@ module Volt
12
12
 
13
13
  # Check if the value is taken
14
14
  # TODO: need a way to handle scope for unique
15
- return $page.store.get(model.path[-2]).where(query).fetch_first do |item|
15
+ return $page.store.get(model.path[-2]).where(query).first do |item|
16
16
  if item
17
17
  message = (args.is_a?(Hash) && args[:message]) || 'is already taken'
18
18
 
@@ -45,8 +45,13 @@ module Volt
45
45
  @removed_listener = @value.on('removed') { |position| item_removed(position) }
46
46
  end
47
47
 
48
- templates_size = @templates.size
49
- values_size = values.size
48
+ templates_size = nil
49
+ values_size = nil
50
+
51
+ Volt.run_in_mode(:no_model_promises) do
52
+ templates_size = @templates.size
53
+ values_size = values.size
54
+ end
50
55
 
51
56
  # Start over, re-create all nodes
52
57
  (templates_size - 1).downto(0) do |index|
@@ -59,65 +64,69 @@ module Volt
59
64
  end
60
65
 
61
66
  def item_removed(position)
62
- # Remove dependency
63
- @templates[position].context.locals[:_index_dependency].remove
64
- @templates[position].context.locals["_#{@item_name}_dependency".to_sym].remove
67
+ Volt.run_in_mode(:no_model_promises) do
68
+ # Remove dependency
69
+ @templates[position].context.locals[:_index_dependency].remove
70
+ @templates[position].context.locals["_#{@item_name}_dependency".to_sym].remove
65
71
 
66
- @templates[position].remove_anchors
67
- @templates[position].remove
68
- @templates.delete_at(position)
72
+ @templates[position].remove_anchors
73
+ @templates[position].remove
74
+ @templates.delete_at(position)
69
75
 
70
- # Removed at the position, update context for every item after this position
71
- update_indexes_after(position)
76
+ # Removed at the position, update context for every item after this position
77
+ update_indexes_after(position)
78
+ end
72
79
  end
73
80
 
74
81
  def item_added(position)
75
- binding_name = @@binding_number
76
- @@binding_number += 1
77
-
78
- if position >= @templates.size
79
- # Setup new bindings in the spot we want to insert the item
80
- dom_section.insert_anchor_before_end(binding_name)
81
- else
82
- # Insert the item before an existing item
83
- dom_section.insert_anchor_before(binding_name, @templates[position].binding_name)
84
- end
82
+ Volt.run_in_mode(:no_model_promises) do
83
+ binding_name = @@binding_number
84
+ @@binding_number += 1
85
+
86
+ if position >= @templates.size
87
+ # Setup new bindings in the spot we want to insert the item
88
+ dom_section.insert_anchor_before_end(binding_name)
89
+ else
90
+ # Insert the item before an existing item
91
+ dom_section.insert_anchor_before(binding_name, @templates[position].binding_name)
92
+ end
85
93
 
86
- # TODORW: :parent => @value may change
87
- item_context = SubContext.new({ _index_value: position, parent: @value }, @context)
88
- item_context.locals[@item_name.to_sym] = proc { @value[item_context.locals[:_index_value]] }
94
+ # TODORW: :parent => @value may change
95
+ item_context = SubContext.new({ _index_value: position, parent: @value }, @context)
96
+ item_context.locals[@item_name.to_sym] = proc { @value[item_context.locals[:_index_value]] }
89
97
 
90
- position_dependency = Dependency.new
91
- item_context.locals[:_index_dependency] = position_dependency
98
+ position_dependency = Dependency.new
99
+ item_context.locals[:_index_dependency] = position_dependency
92
100
 
93
- # Get and set index
94
- item_context.locals[:_index=] = proc do |val|
95
- position_dependency.changed!
96
- item_context.locals[:_index_value] = val
97
- end
101
+ # Get and set index
102
+ item_context.locals[:_index=] = proc do |val|
103
+ position_dependency.changed!
104
+ item_context.locals[:_index_value] = val
105
+ end
98
106
 
99
- # Get and set value
100
- value_dependency = Dependency.new
101
- item_context.locals["_#{@item_name}_dependency".to_sym] = value_dependency
107
+ # Get and set value
108
+ value_dependency = Dependency.new
109
+ item_context.locals["_#{@item_name}_dependency".to_sym] = value_dependency
102
110
 
103
- item_context.locals["#{@item_name}=".to_sym] = proc do |val|
104
- value_dependency.changed!
105
- @value[item_context.locals[:_index_value]] = val
106
- end
111
+ item_context.locals["#{@item_name}=".to_sym] = proc do |val|
112
+ value_dependency.changed!
113
+ @value[item_context.locals[:_index_value]] = val
114
+ end
107
115
 
108
- # If the user provides an each_with_index, we can assign the lookup for the index
109
- # variable here.
110
- if @index_name
111
- item_context.locals[@index_name.to_sym] = proc do
112
- position_dependency.depend
113
- item_context.locals[:_index_value]
116
+ # If the user provides an each_with_index, we can assign the lookup for the index
117
+ # variable here.
118
+ if @index_name
119
+ item_context.locals[@index_name.to_sym] = proc do
120
+ position_dependency.depend
121
+ item_context.locals[:_index_value]
122
+ end
114
123
  end
115
- end
116
124
 
117
- item_template = TemplateRenderer.new(@volt_app, @target, item_context, binding_name, @template_name)
118
- @templates.insert(position, item_template)
125
+ item_template = TemplateRenderer.new(@volt_app, @target, item_context, binding_name, @template_name)
126
+ @templates.insert(position, item_template)
119
127
 
120
- update_indexes_after(position)
128
+ update_indexes_after(position)
129
+ end
121
130
  end
122
131
 
123
132
  # When items are added or removed in the middle of the list, we need
@@ -6,7 +6,7 @@ module Volt
6
6
  def initialize(volt_app)
7
7
  @volt_app = volt_app
8
8
  # Run the code to setup the page
9
- @page = Model.new
9
+ @page = PageRoot.new
10
10
 
11
11
  @url = URL.new
12
12
  @params = @url.params
@@ -47,19 +47,19 @@ module Volt
47
47
  end
48
48
 
49
49
  def flash
50
- @flash ||= Model.new({}, persistor: Persistors::Flash)
50
+ @flash ||= FlashRoot.new({}, persistor: Persistors::Flash)
51
51
  end
52
52
 
53
53
  def store
54
- @store ||= Model.new({}, persistor: Persistors::StoreFactory.new(tasks))
54
+ @store ||= StoreRoot.new({}, persistor: Persistors::StoreFactory.new(tasks))
55
55
  end
56
56
 
57
57
  def local_store
58
- @local_store ||= Model.new({}, persistor: Persistors::LocalStore)
58
+ @local_store ||= LocalStoreRoot.new({}, persistor: Persistors::LocalStore)
59
59
  end
60
60
 
61
61
  def cookies
62
- @cookies ||= Model.new({}, persistor: Persistors::Cookies)
62
+ @cookies ||= CookiesRoot.new({}, persistor: Persistors::Cookies)
63
63
  end
64
64
 
65
65
  def tasks
@@ -39,8 +39,10 @@ module Volt
39
39
  if block
40
40
  count = 0
41
41
 
42
- size.times do |index|
43
- count += 1 if block.call(self[index])
42
+ Volt.run_in_mode(:no_model_promises) do
43
+ size.times do |index|
44
+ count += 1 if block.call(self[index])
45
+ end
44
46
  end
45
47
 
46
48
  count
@@ -90,7 +92,7 @@ module Volt
90
92
  # TODO: Handle a range
91
93
  def [](index)
92
94
  # Handle a negative index, depend on size
93
- index = size + index if index < 0
95
+ index = @array.size + index if index < 0
94
96
 
95
97
  # Get or create the dependency
96
98
  dep = (@array_deps[index] ||= Dependency.new)
@@ -120,6 +122,7 @@ module Volt
120
122
  alias_method :length, :size
121
123
 
122
124
  def delete_at(index)
125
+ size = @array.size
123
126
  # Handle a negative index
124
127
  index = size + index if index < 0
125
128
 
@@ -183,8 +186,8 @@ module Volt
183
186
  def <<(value)
184
187
  result = (@array << value)
185
188
 
186
- trigger_for_index!(size - 1)
187
- trigger_added!(size - 1)
189
+ trigger_for_index!(@array.size - 1)
190
+ trigger_added!(@array.size - 1)
188
191
  trigger_size_change!
189
192
 
190
193
  result
@@ -192,7 +195,7 @@ module Volt
192
195
 
193
196
  def +(array)
194
197
  fail 'not implemented yet'
195
- old_size = size
198
+ old_size = @array.size
196
199
 
197
200
  # TODO: += is funky here, might need to make a .plus! method
198
201
  result = ReactiveArray.new(@array.dup + array)