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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/app/volt/tasks/query_tasks.rb +0 -7
- data/app/volt/tasks/store_tasks.rb +0 -6
- data/docs/UPGRADE_GUIDE.md +2 -0
- data/lib/volt/cli/asset_compile.rb +2 -2
- data/lib/volt/cli/console.rb +21 -0
- data/lib/volt/config.rb +0 -10
- data/lib/volt/controllers/collection_helpers.rb +18 -0
- data/lib/volt/controllers/model_controller.rb +2 -12
- data/lib/volt/extra_core/object.rb +19 -0
- data/lib/volt/models.rb +14 -9
- data/lib/volt/models/array_model.rb +62 -22
- data/lib/volt/models/associations.rb +16 -1
- data/lib/volt/models/model.rb +27 -15
- data/lib/volt/models/model_helpers/model_helpers.rb +29 -0
- data/lib/volt/models/permissions.rb +15 -4
- data/lib/volt/models/persistors/array_store.rb +40 -0
- data/lib/volt/models/persistors/model_store.rb +2 -2
- data/lib/volt/models/persistors/query/query_listener.rb +3 -1
- data/lib/volt/models/persistors/store.rb +2 -1
- data/lib/volt/models/root_models/root_models.rb +31 -0
- data/lib/volt/models/root_models/store_root.rb +36 -0
- data/lib/volt/models/validators/unique_validator.rb +1 -1
- data/lib/volt/page/bindings/each_binding.rb +56 -47
- data/lib/volt/page/page.rb +5 -5
- data/lib/volt/reactive/reactive_array.rb +9 -6
- data/lib/volt/server.rb +2 -2
- data/lib/volt/server/component_templates.rb +7 -4
- data/lib/volt/server/message_bus/message_encoder.rb +9 -1
- data/lib/volt/server/rack/component_code.rb +8 -1
- data/lib/volt/server/rack/index_files.rb +5 -2
- data/lib/volt/tasks/{task_handler.rb → task.rb} +6 -6
- data/lib/volt/utils/promise.rb +429 -0
- data/lib/volt/utils/promise_extensions.rb +79 -0
- data/lib/volt/version.rb +1 -1
- data/lib/volt/volt/app.rb +5 -2
- data/lib/volt/volt/server_setup/app.rb +28 -7
- data/spec/apps/kitchen_sink/app/main/controllers/server/simple_http_controller.rb +1 -1
- data/spec/apps/kitchen_sink/app/main/views/main/store.html +3 -0
- data/spec/extra_core/object_spec.rb +13 -0
- data/spec/integration/store_spec.rb +10 -0
- data/spec/models/associations_spec.rb +48 -26
- data/spec/models/model_spec.rb +23 -7
- data/spec/models/persistors/store_spec.rb +28 -0
- data/spec/models/validators/unique_validator_spec.rb +1 -1
- data/spec/spec_helper.rb +4 -1
- data/spec/utils/promise_extensions_spec.rb +42 -0
- data/templates/component/config/initializers/boot.rb +10 -0
- data/templates/{project/app → component/config/initializers/client}/.empty_directory +0 -0
- data/templates/component/config/initializers/server/.empty_directory +0 -0
- data/templates/newgem/app/newgem/config/initializers/client/.empty_directory +0 -0
- data/templates/newgem/app/newgem/config/initializers/server/.empty_directory +0 -0
- data/templates/project/Gemfile.tt +6 -2
- data/templates/project/app/main/config/initializers/boot.rb +10 -0
- data/templates/project/app/main/config/initializers/client/.empty_directory +0 -0
- data/templates/project/app/main/config/initializers/server/.empty_directory +0 -0
- data/templates/project/config/app.rb.tt +3 -0
- data/templates/project/config/initializers/client/.empty_directory +0 -0
- data/templates/project/config/initializers/server/.empty_directory +0 -0
- metadata +22 -5
- 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
|
-
|
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
|
-
|
165
|
+
result = @attributes.reject { |key| key != :id }
|
164
166
|
elsif deny && deny.size > 0
|
165
167
|
# Reject any in the deny list
|
166
|
-
|
168
|
+
result = @attributes.reject { |key| deny.include?(key) }
|
167
169
|
else
|
168
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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).
|
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 =
|
49
|
-
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
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
72
|
+
@templates[position].remove_anchors
|
73
|
+
@templates[position].remove
|
74
|
+
@templates.delete_at(position)
|
69
75
|
|
70
|
-
|
71
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
91
|
-
|
98
|
+
position_dependency = Dependency.new
|
99
|
+
item_context.locals[:_index_dependency] = position_dependency
|
92
100
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
100
|
-
|
101
|
-
|
107
|
+
# Get and set value
|
108
|
+
value_dependency = Dependency.new
|
109
|
+
item_context.locals["_#{@item_name}_dependency".to_sym] = value_dependency
|
102
110
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
-
|
118
|
-
|
125
|
+
item_template = TemplateRenderer.new(@volt_app, @target, item_context, binding_name, @template_name)
|
126
|
+
@templates.insert(position, item_template)
|
119
127
|
|
120
|
-
|
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
|
data/lib/volt/page/page.rb
CHANGED
@@ -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 =
|
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 ||=
|
50
|
+
@flash ||= FlashRoot.new({}, persistor: Persistors::Flash)
|
51
51
|
end
|
52
52
|
|
53
53
|
def store
|
54
|
-
@store ||=
|
54
|
+
@store ||= StoreRoot.new({}, persistor: Persistors::StoreFactory.new(tasks))
|
55
55
|
end
|
56
56
|
|
57
57
|
def local_store
|
58
|
-
@local_store ||=
|
58
|
+
@local_store ||= LocalStoreRoot.new({}, persistor: Persistors::LocalStore)
|
59
59
|
end
|
60
60
|
|
61
61
|
def cookies
|
62
|
-
@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
|
-
|
43
|
-
|
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)
|