volt 0.8.18 → 0.8.19
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 +14 -0
- data/Readme.md +2 -1
- data/VERSION +1 -1
- data/app/volt/controllers/notices_controller.rb +12 -2
- data/app/volt/models/user.rb +34 -1
- data/app/volt/tasks/store_tasks.rb +26 -23
- data/app/volt/tasks/user_tasks.rb +26 -2
- data/app/volt/views/notices/index.html +8 -6
- data/lib/volt.rb +47 -1
- data/lib/volt/cli/asset_compile.rb +24 -42
- data/lib/volt/config.rb +24 -14
- data/lib/volt/controllers/model_controller.rb +6 -1
- data/lib/volt/data_stores/mongo_driver.rb +7 -3
- data/lib/volt/models.rb +3 -0
- data/lib/volt/models/array_model.rb +21 -3
- data/lib/volt/models/model.rb +109 -48
- data/lib/volt/models/model_hash_behaviour.rb +25 -8
- data/lib/volt/models/persistors/array_store.rb +5 -2
- data/lib/volt/models/persistors/cookies.rb +91 -0
- data/lib/volt/models/persistors/model_store.rb +52 -14
- data/lib/volt/models/persistors/query/query_listener.rb +4 -1
- data/lib/volt/models/persistors/query/query_listener_pool.rb +1 -1
- data/lib/volt/models/persistors/store_state.rb +5 -5
- data/lib/volt/models/validations.rb +87 -18
- data/lib/volt/models/validators/length_validator.rb +1 -1
- data/lib/volt/models/validators/presence_validator.rb +1 -1
- data/lib/volt/models/validators/unique_validator.rb +28 -0
- data/lib/volt/page/bindings/template_binding.rb +2 -2
- data/lib/volt/page/page.rb +4 -0
- data/lib/volt/page/tasks.rb +2 -2
- data/lib/volt/reactive/computation.rb +2 -0
- data/lib/volt/reactive/hash_dependency.rb +1 -3
- data/lib/volt/reactive/reactive_array.rb +7 -0
- data/lib/volt/reactive/reactive_hash.rb +17 -0
- data/lib/volt/server.rb +1 -1
- data/lib/volt/server/component_templates.rb +3 -6
- data/lib/volt/server/html_parser/view_scope.rb +1 -1
- data/lib/volt/server/rack/component_code.rb +3 -2
- data/lib/volt/server/rack/component_paths.rb +15 -0
- data/lib/volt/server/rack/index_files.rb +1 -1
- data/lib/volt/tasks/dispatcher.rb +26 -12
- data/lib/volt/tasks/task_handler.rb +17 -15
- data/spec/apps/kitchen_sink/app/main/config/routes.rb +3 -0
- data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +26 -0
- data/spec/apps/kitchen_sink/app/main/controllers/users_test_controller.rb +27 -0
- data/spec/apps/kitchen_sink/app/main/views/main/cookie_test.html +25 -0
- data/spec/apps/kitchen_sink/app/main/views/main/flash.html +13 -0
- data/spec/apps/kitchen_sink/app/main/views/main/main.html +3 -0
- data/spec/apps/kitchen_sink/app/main/views/users_test/index.html +22 -0
- data/spec/apps/kitchen_sink/config/app.rb +3 -0
- data/spec/integration/bindings_spec.rb +1 -1
- data/spec/integration/cookies_spec.rb +48 -0
- data/spec/integration/flash_spec.rb +32 -0
- data/spec/models/model_spec.rb +3 -4
- data/spec/models/validations_spec.rb +13 -13
- data/spec/tasks/dispatcher_spec.rb +1 -1
- data/templates/project/config/app.rb.tt +6 -1
- data/templates/project/{public → config/base_page}/index.html +0 -0
- data/volt.gemspec +4 -0
- metadata +47 -3
@@ -25,6 +25,8 @@ module Volt
|
|
25
25
|
def add_to_collection
|
26
26
|
@in_collection = true
|
27
27
|
ensure_setup
|
28
|
+
|
29
|
+
# Call changed, return the promise
|
28
30
|
changed
|
29
31
|
end
|
30
32
|
|
@@ -84,14 +86,12 @@ module Volt
|
|
84
86
|
fail 'Attempting to save model directly on store.'
|
85
87
|
else
|
86
88
|
if RUBY_PLATFORM == 'opal'
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
promise.reject(errors)
|
92
|
-
end
|
93
|
-
end
|
89
|
+
@save_promises ||= []
|
90
|
+
@save_promises << promise
|
91
|
+
|
92
|
+
queue_client_save
|
94
93
|
else
|
94
|
+
puts "Save to DB"
|
95
95
|
errors = save_to_db!(self_attributes)
|
96
96
|
if errors.size == 0
|
97
97
|
promise.resolve(nil)
|
@@ -101,9 +101,40 @@ module Volt
|
|
101
101
|
end
|
102
102
|
end
|
103
103
|
end
|
104
|
+
|
104
105
|
promise
|
105
106
|
end
|
106
107
|
|
108
|
+
def queue_client_save
|
109
|
+
`
|
110
|
+
if (!self.saveTimer) {
|
111
|
+
self.saveTimer = setImmediate(self.$run_save.bind(self));
|
112
|
+
}
|
113
|
+
`
|
114
|
+
end
|
115
|
+
|
116
|
+
# Run save is called on the client side after a queued setImmediate. It does the
|
117
|
+
# saving on the front-end. Adding a setImmediate allows multiple changes to be
|
118
|
+
# batched together.
|
119
|
+
def run_save
|
120
|
+
# Clear the save timer
|
121
|
+
`
|
122
|
+
clearImmediate(self.saveTimer);
|
123
|
+
delete self.saveTimer;
|
124
|
+
`
|
125
|
+
|
126
|
+
StoreTasks.save(collection, @model.path, self_attributes).then do
|
127
|
+
save_promises = @save_promises
|
128
|
+
@save_promises = nil
|
129
|
+
save_promises.each {|promise| promise.resolve(nil) }
|
130
|
+
end.fail do |errors|
|
131
|
+
save_promises = @save_promises
|
132
|
+
@save_promises = nil
|
133
|
+
save_promises.each {|promise| promise.reject(errors) }
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
107
138
|
def event_added(event, first, first_for_event)
|
108
139
|
if first_for_event && event == :changed
|
109
140
|
ensure_setup
|
@@ -113,12 +144,14 @@ module Volt
|
|
113
144
|
# Update the models based on the id/identity map. Usually these requests
|
114
145
|
# will come from the backend.
|
115
146
|
def self.changed(model_id, data)
|
116
|
-
|
147
|
+
Model.nosave do
|
148
|
+
model = @@identity_map.lookup(model_id)
|
117
149
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
150
|
+
if model
|
151
|
+
data.each_pair do |key, value|
|
152
|
+
if key != :_id
|
153
|
+
model.send(:"_#{key}=", value)
|
154
|
+
end
|
122
155
|
end
|
123
156
|
end
|
124
157
|
end
|
@@ -147,6 +180,11 @@ module Volt
|
|
147
180
|
|
148
181
|
# Do the actual writing of data to the database, only runs on the backend.
|
149
182
|
def save_to_db!(values)
|
183
|
+
# Check to make sure the model has no validation errors.
|
184
|
+
errors = @model.errors
|
185
|
+
return errors if errors.present?
|
186
|
+
|
187
|
+
# Passed, save it
|
150
188
|
id = values[:_id]
|
151
189
|
|
152
190
|
# Try to create
|
@@ -166,8 +204,8 @@ module Volt
|
|
166
204
|
end
|
167
205
|
end
|
168
206
|
|
169
|
-
|
170
|
-
QueryTasks.live_query_pool.updated_collection(collection.to_s, Thread.current['
|
207
|
+
puts "Update Collection: #{collection.inspect} - #{values.inspect} -- #{Thread.current['in_channel'].inspect}"
|
208
|
+
QueryTasks.live_query_pool.updated_collection(collection.to_s, Thread.current['in_channel'])
|
171
209
|
return {}
|
172
210
|
end
|
173
211
|
end
|
@@ -33,7 +33,7 @@ module Volt
|
|
33
33
|
store.change_state_to(:loaded)
|
34
34
|
end
|
35
35
|
end.fail do |err|
|
36
|
-
puts "
|
36
|
+
puts "Error adding listener: #{err.inspect}"
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
@@ -47,6 +47,8 @@ module Volt
|
|
47
47
|
@stores.first.model.each_with_index do |item, index|
|
48
48
|
store.add(index, item.to_h)
|
49
49
|
end
|
50
|
+
|
51
|
+
store.change_state_to(:loaded)
|
50
52
|
else
|
51
53
|
# First time we've added a store, setup the listener and get
|
52
54
|
# the initial data.
|
@@ -84,6 +86,7 @@ module Volt
|
|
84
86
|
|
85
87
|
def changed(model_id, data)
|
86
88
|
$loading_models = true
|
89
|
+
puts "new data: #{data.inspect}"
|
87
90
|
Persistors::ModelStore.changed(model_id, data)
|
88
91
|
$loading_models = false
|
89
92
|
end
|
@@ -10,19 +10,18 @@ module Volt
|
|
10
10
|
def state
|
11
11
|
@state_dep ||= Dependency.new
|
12
12
|
@state_dep.depend
|
13
|
+
|
13
14
|
@state
|
14
15
|
end
|
15
16
|
|
16
17
|
# Called from the QueryListener when the data is loaded
|
17
|
-
def change_state_to(new_state
|
18
|
+
def change_state_to(new_state)
|
18
19
|
old_state = @state
|
19
20
|
@state = new_state
|
20
21
|
|
21
22
|
# Trigger changed on the 'state' method
|
22
|
-
|
23
|
-
if
|
24
|
-
@state_dep.changed! if @state_dep
|
25
|
-
end
|
23
|
+
if old_state != @state
|
24
|
+
@state_dep.changed! if @state_dep
|
26
25
|
end
|
27
26
|
|
28
27
|
if @state == :loaded && @fetch_promises
|
@@ -30,6 +29,7 @@ module Volt
|
|
30
29
|
@fetch_promises.compact.each { |fp| fp.resolve(@model) }
|
31
30
|
@fetch_promises = nil
|
32
31
|
|
32
|
+
# puts "STOP LIST---------"
|
33
33
|
stop_listening
|
34
34
|
end
|
35
35
|
end
|
@@ -1,17 +1,33 @@
|
|
1
1
|
# require 'volt/models/validations/errors'
|
2
2
|
require 'volt/models/validators/length_validator'
|
3
3
|
require 'volt/models/validators/presence_validator'
|
4
|
+
require 'volt/models/validators/unique_validator'
|
4
5
|
|
5
6
|
module Volt
|
6
7
|
# Include in any class to get validation logic
|
7
8
|
module Validations
|
8
9
|
module ClassMethods
|
9
|
-
def validate(field_name, options)
|
10
|
-
|
11
|
-
|
10
|
+
def validate(field_name=nil, options=nil, &block)
|
11
|
+
if block
|
12
|
+
if field_name || options
|
13
|
+
raise "validate should be passed a field name and options or a block, not both."
|
14
|
+
end
|
15
|
+
@custom_validations ||= []
|
16
|
+
@custom_validations << block
|
17
|
+
else
|
18
|
+
@validations ||= {}
|
19
|
+
@validations[field_name] = options
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# TODO: For some reason attr_reader on a class doesn't work in Opal
|
24
|
+
def validations
|
25
|
+
@validations
|
12
26
|
end
|
13
27
|
|
14
|
-
|
28
|
+
def custom_validations
|
29
|
+
@custom_validations
|
30
|
+
end
|
15
31
|
end
|
16
32
|
|
17
33
|
def self.included(base)
|
@@ -20,7 +36,7 @@ module Volt
|
|
20
36
|
|
21
37
|
# Once a field is ready, we can use include_in_errors! to start
|
22
38
|
# showing its errors.
|
23
|
-
def mark_field!(field_name
|
39
|
+
def mark_field!(field_name)
|
24
40
|
marked_fields[field_name] = true
|
25
41
|
end
|
26
42
|
|
@@ -28,31 +44,69 @@ module Volt
|
|
28
44
|
@marked_fields ||= ReactiveHash.new
|
29
45
|
end
|
30
46
|
|
47
|
+
|
48
|
+
# Marks all fields, useful for when a model saves.
|
49
|
+
def mark_all_fields!
|
50
|
+
validations = self.class.validations
|
51
|
+
if validations
|
52
|
+
validations.each_key do |key|
|
53
|
+
mark_field!(key.to_sym)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
31
59
|
def marked_errors
|
32
60
|
errors(true)
|
33
61
|
end
|
34
62
|
|
63
|
+
# server errors are errors that come back from the server when we save!
|
64
|
+
# Any changes to the associated fields will clear the error until another
|
65
|
+
# save!
|
66
|
+
def server_errors
|
67
|
+
@server_errors ||= ReactiveHash.new
|
68
|
+
end
|
69
|
+
|
70
|
+
# When a field is changed, we want to clear any errors from the server
|
71
|
+
def clear_server_errors(key)
|
72
|
+
@server_errors.delete(key)
|
73
|
+
end
|
74
|
+
|
35
75
|
# TODO: Errors is being called for any validation change. We should have errors return a
|
36
76
|
# hash like object that only calls the validation for each one.
|
37
77
|
def errors(marked_only = false)
|
38
78
|
errors = {}
|
39
79
|
|
40
|
-
|
80
|
+
# Merge into errors, combining any error arrays
|
81
|
+
merge = proc do |new_errors|
|
82
|
+
errors.merge!(new_errors) do |key, new_val, old_val|
|
83
|
+
new_val + old_val
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
errors = run_validations(errors, merge, marked_only)
|
41
88
|
|
89
|
+
# See if any server errors are in place and merge them in if they are
|
90
|
+
if Volt.client?
|
91
|
+
errors = merge.call(server_errors.to_h)
|
92
|
+
end
|
93
|
+
|
94
|
+
errors = run_custom_validations(errors, merge)
|
95
|
+
|
96
|
+
errors
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
# Runs through each of the normal validations.
|
102
|
+
def run_validations(errors, merge, marked_only)
|
103
|
+
validations = self.class.validations
|
42
104
|
if validations
|
43
|
-
# Merge into errors, combining any error arrays
|
44
|
-
merge = proc do |new_errors|
|
45
|
-
errors.merge!(new_errors) do |key, new_val, old_val|
|
46
|
-
new_val + old_val
|
47
|
-
end
|
48
|
-
end
|
49
105
|
|
50
106
|
# Run through each validation
|
51
107
|
validations.each_pair do |field_name, options|
|
52
|
-
|
53
|
-
|
54
|
-
next unless marked_fields[field_name]
|
55
|
-
end
|
108
|
+
# When marked only, skip any validations on non-marked fields
|
109
|
+
next if marked_only && !marked_fields[field_name]
|
56
110
|
|
57
111
|
options.each_pair do |validation, args|
|
58
112
|
# Call the specific validator, then merge the results back
|
@@ -68,10 +122,25 @@ module Volt
|
|
68
122
|
end
|
69
123
|
end
|
70
124
|
|
71
|
-
errors
|
125
|
+
return errors
|
126
|
+
end
|
127
|
+
|
128
|
+
def run_custom_validations(errors, merge)
|
129
|
+
# Call all of the custom validations
|
130
|
+
custom_validations = self.class.custom_validations
|
131
|
+
if custom_validations
|
132
|
+
custom_validations.each do |custom_validation|
|
133
|
+
# Run the validator in the context of the model
|
134
|
+
result = instance_eval(&custom_validation)
|
135
|
+
if result
|
136
|
+
errors = merge.call(result)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
return errors
|
72
142
|
end
|
73
143
|
|
74
|
-
private
|
75
144
|
|
76
145
|
# calls the validate method on the class, passing the right arguments.
|
77
146
|
def validate_with(merge, klass, field_name, args)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Volt
|
2
|
+
class UniqueValidator
|
3
|
+
def self.validate(model, field_name, args)
|
4
|
+
errors = {}
|
5
|
+
|
6
|
+
if RUBY_PLATFORM != 'opal'
|
7
|
+
if args
|
8
|
+
value = model.read_attribute(field_name)
|
9
|
+
|
10
|
+
query = {}
|
11
|
+
# Check to see if any other documents have this value.
|
12
|
+
query[field_name.to_s] = value
|
13
|
+
query['_id'] = {'$ne' => model._id}
|
14
|
+
|
15
|
+
# Check if the value is taken
|
16
|
+
# TODO: need a way to handle scope for unique
|
17
|
+
if $page.store.send(:"_#{model.path[-2]}").find(query).size > 0
|
18
|
+
message = (args.is_a?(Hash) && args[:message]) || "is already taken"
|
19
|
+
|
20
|
+
errors[field_name] = [message]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
errors
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -53,7 +53,7 @@ module Volt
|
|
53
53
|
# The defaults are as follows:
|
54
54
|
# 1. component - main
|
55
55
|
# 2. controller - main
|
56
|
-
# 3. view -
|
56
|
+
# 3. view - index
|
57
57
|
# 4. section - body
|
58
58
|
def path_for_template(lookup_path, force_section = nil)
|
59
59
|
parts = lookup_path.split('/')
|
@@ -243,7 +243,7 @@ module Volt
|
|
243
243
|
action = controller_path[-1]
|
244
244
|
|
245
245
|
# Get the constant parts
|
246
|
-
parts = controller_path[0..-2].map { |v| v.
|
246
|
+
parts = controller_path[0..-2].map { |v| v.tr('-', '_').camelize }
|
247
247
|
|
248
248
|
# Home doesn't get namespaced
|
249
249
|
if parts.first == 'Main'
|
data/lib/volt/page/page.rb
CHANGED
data/lib/volt/page/tasks.rb
CHANGED
@@ -12,7 +12,7 @@ module Volt
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
def call(class_name, method_name, *args)
|
15
|
+
def call(class_name, method_name, meta_data, *args)
|
16
16
|
promise_id = @promise_id
|
17
17
|
@promise_id += 1
|
18
18
|
|
@@ -22,7 +22,7 @@ module Volt
|
|
22
22
|
|
23
23
|
# TODO: Timeout on these callbacks
|
24
24
|
|
25
|
-
@page.channel.send_message([promise_id, class_name, method_name, *args])
|
25
|
+
@page.channel.send_message([promise_id, class_name, method_name, meta_data, *args])
|
26
26
|
|
27
27
|
promise
|
28
28
|
end
|