volt 0.9.3.pre2 → 0.9.3.pre3
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.
- 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)
|