volt 0.9.5.pre3 → 0.9.5.pre4
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 +2 -0
- data/app/volt/models/active_volt_instance.rb +6 -0
- data/app/volt/tasks/live_query/live_query.rb +2 -2
- data/app/volt/tasks/query_tasks.rb +1 -1
- data/lib/volt/models.rb +9 -11
- data/lib/volt/models/array_model.rb +80 -36
- data/lib/volt/models/field_helpers.rb +5 -1
- data/lib/volt/models/permissions.rb +81 -72
- data/lib/volt/models/persistors/array_store.rb +5 -25
- data/lib/volt/models/root_models/store_root.rb +0 -1
- data/lib/volt/reactive/reactive_array.rb +4 -1
- data/lib/volt/server/message_bus/peer_to_peer.rb +2 -2
- data/lib/volt/server/message_bus/peer_to_peer/server_tracker.rb +1 -1
- data/lib/volt/server/middleware/default_middleware_stack.rb +3 -2
- data/lib/volt/server/rack/asset_files.rb +18 -0
- data/lib/volt/server/rack/component_code.rb +1 -1
- data/lib/volt/server/rack/index_files.rb +2 -2
- data/lib/volt/server/rack/opal_files.rb +3 -0
- data/lib/volt/spec/setup.rb +0 -2
- data/lib/volt/utils/promise_extensions.rb +4 -3
- data/lib/volt/version.rb +1 -1
- data/lib/volt/volt/app.rb +5 -0
- data/lib/volt/volt/users.rb +2 -2
- data/spec/extra_core/class_spec.rb +11 -0
- data/spec/integration/bindings_spec.rb +0 -1
- data/spec/models/array_model_spec.rb +16 -0
- data/spec/models/field_helpers_spec.rb +8 -1
- data/spec/models/permissions_spec.rb +25 -0
- data/volt.gemspec +1 -1
- metadata +5 -5
- data/lib/volt/utils/promise.rb +0 -429
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 237e158164bd2f394e80998fa3314f52024fbebc
|
4
|
+
data.tar.gz: abd1d018ffd679afba929518326f0139cce4deb0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 48b5fa1a075beab16bbe901532b11e46e6ee4aa065d21db169bc425efc9def807d2d286f5ea4a2f6b569458b05fcb7052721ceb3df26aa7d585990a1d4fe6ffb
|
7
|
+
data.tar.gz: d18c325ef1f789d19a6ceaf2327452fd5b06dcefda74d0eeeca8e66b070599544ebf5176779d465a67bf641987f9af40de34a3eb7bbcffcd4b8d469910b17aea
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,8 @@
|
|
5
5
|
- You can now disable auto-import of JS/CSS with ```disable_auto_import``` in a dependencies.rb file
|
6
6
|
- Opal was upgraded to 0.8, which brings sourcemaps back (yah!)
|
7
7
|
- Page load performance was improved, and more of sprockets was used for component loading.
|
8
|
+
- You can now return promises in permissions blocks. Also, can_read?, can_create?, and .can_delete?
|
9
|
+
- Anything in /public is now served via Rack::Static in the default middleware stack. (So you can put user uploaded images in there)
|
8
10
|
|
9
11
|
## 0.9.4
|
10
12
|
### Lingo Change
|
@@ -37,7 +37,7 @@ class LiveQuery
|
|
37
37
|
notify! do |channel|
|
38
38
|
filtered_data = nil
|
39
39
|
Volt.as_user(channel.user_id) do
|
40
|
-
filtered_data = model.filtered_attributes
|
40
|
+
filtered_data = model.filtered_attributes.sync
|
41
41
|
end
|
42
42
|
|
43
43
|
channel.send_message('added', nil, @collection, @query, index, filtered_data)
|
@@ -57,7 +57,7 @@ class LiveQuery
|
|
57
57
|
notify!(skip_channel) do |channel|
|
58
58
|
filtered_data = nil
|
59
59
|
Volt.as_user(channel.user_id) do
|
60
|
-
filtered_data = model.filtered_attributes
|
60
|
+
filtered_data = model.filtered_attributes.sync
|
61
61
|
end
|
62
62
|
# puts "Changed: #{id}, #{data} to #{channel.inspect}"
|
63
63
|
channel.send_message('changed', nil, @collection, @query, id, filtered_data)
|
@@ -26,7 +26,7 @@ class QueryTasks < Volt::Task
|
|
26
26
|
if initial_data
|
27
27
|
# Only send the filtered attributes for this user
|
28
28
|
initial_data.map! do |data|
|
29
|
-
[data[0], live_query.model_for_filter(data[1]).filtered_attributes]
|
29
|
+
[data[0], live_query.model_for_filter(data[1]).filtered_attributes.sync]
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
data/lib/volt/models.rb
CHANGED
@@ -12,16 +12,14 @@ require 'volt/models/persistors/local_store'
|
|
12
12
|
require 'volt/models/root_models/root_models'
|
13
13
|
# require 'volt/models/root_models/store_root'
|
14
14
|
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
#
|
20
|
-
|
21
|
-
# require 'opal'
|
15
|
+
# Requrie in opal's promise library
|
16
|
+
if RUBY_PLATFORM == 'opal'
|
17
|
+
require 'promise'
|
18
|
+
else
|
19
|
+
# Opal doesn't expose its promise library directly
|
20
|
+
require 'opal'
|
22
21
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
# TODO: remove once https://github.com/opal/opal/pull/725 is released.
|
22
|
+
gem_dir = File.join(Opal.gem_dir, '..')
|
23
|
+
require(gem_dir + '/stdlib/promise')
|
24
|
+
end
|
27
25
|
require 'volt/utils/promise_extensions'
|
@@ -22,21 +22,50 @@ module Volt
|
|
22
22
|
# of immediately returning). To accomplish this, we call the
|
23
23
|
# #run_once_loaded method on the persistor.
|
24
24
|
def self.proxy_with_load(*method_names)
|
25
|
+
imethods = instance_methods(false)
|
25
26
|
method_names.each do |method_name|
|
26
|
-
|
27
|
-
|
27
|
+
# Sometimes we want to alias a method_missing method, so we use super
|
28
|
+
# instead to call it, if its not defined locally.
|
29
|
+
if imethods.include?(method_name)
|
30
|
+
imethod = true
|
31
|
+
old_method_name = :"__old_#{method_name}"
|
32
|
+
alias_method(old_method_name, method_name)
|
33
|
+
else
|
34
|
+
imethod = false
|
35
|
+
end
|
28
36
|
|
29
37
|
define_method(method_name) do |*args, &block|
|
38
|
+
if imethod
|
39
|
+
call_orig = proc do |*args|
|
40
|
+
send(old_method_name, *args)
|
41
|
+
end
|
42
|
+
else
|
43
|
+
call_orig = proc do |*args|
|
44
|
+
super(*args)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
30
48
|
# track on the root dep
|
31
49
|
persistor.try(:root_dep).try(:depend)
|
32
50
|
|
33
51
|
if persistor.respond_to?(:run_once_loaded) &&
|
34
52
|
!Volt.in_mode?(:no_model_promises)
|
35
|
-
persistor.run_once_loaded
|
36
|
-
|
53
|
+
promise = persistor.run_once_loaded.then do
|
54
|
+
# We are already loaded and the result is going to be wrapped
|
55
|
+
Volt.run_in_mode(:no_model_promises) do
|
56
|
+
call_orig.call(*args)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
if block
|
61
|
+
promise = promise.then do |val|
|
62
|
+
block.call(val)
|
63
|
+
end
|
37
64
|
end
|
65
|
+
|
66
|
+
promise
|
38
67
|
else
|
39
|
-
|
68
|
+
call_orig.call(*args)
|
40
69
|
end
|
41
70
|
end
|
42
71
|
end
|
@@ -119,11 +148,17 @@ module Volt
|
|
119
148
|
|
120
149
|
def delete(val)
|
121
150
|
# Check to make sure the models are allowed to be deleted
|
122
|
-
if !val.is_a?(Model)
|
123
|
-
|
124
|
-
|
151
|
+
if !val.is_a?(Model)
|
152
|
+
# Not a model, return as a Promise
|
153
|
+
super(val).then
|
125
154
|
else
|
126
|
-
|
155
|
+
val.can_delete?.then do |can_delete|
|
156
|
+
if can_delete
|
157
|
+
super(val)
|
158
|
+
else
|
159
|
+
Promise.new.reject("permissions did not allow delete for #{val.inspect}.")
|
160
|
+
end
|
161
|
+
end
|
127
162
|
end
|
128
163
|
end
|
129
164
|
|
@@ -199,6 +234,10 @@ module Volt
|
|
199
234
|
super(*args)
|
200
235
|
end
|
201
236
|
|
237
|
+
def flatten(*args)
|
238
|
+
wrap_values(to_a.flatten(*args))
|
239
|
+
end
|
240
|
+
|
202
241
|
def new_model(*args)
|
203
242
|
Volt::Model.class_at_path(options[:path]).new(*args)
|
204
243
|
end
|
@@ -285,34 +324,39 @@ module Volt
|
|
285
324
|
|
286
325
|
|
287
326
|
if model.is_a?(Model)
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
reactive_array_append(model)
|
294
|
-
|
295
|
-
@persistor.added(model, @array.size - 1)
|
296
|
-
|
297
|
-
# Validate and save
|
298
|
-
promise = model.run_changed.then do
|
299
|
-
# Mark the model as not new
|
300
|
-
model.instance_variable_set('@new', false)
|
301
|
-
|
302
|
-
# Mark the model as loaded
|
303
|
-
model.change_state_to(:loaded_state, :loaded)
|
304
|
-
|
305
|
-
end.fail do |err|
|
306
|
-
# remove from the collection because it failed to save on the server
|
307
|
-
# we don't need to call delete on the server.
|
308
|
-
index = @array.index(model)
|
309
|
-
delete_at(index, true)
|
327
|
+
promise = model.can_create?.then do |can_create|
|
328
|
+
unless can_create
|
329
|
+
fail "permissions did not allow create for #{model.inspect}"
|
330
|
+
end
|
331
|
+
end.then do
|
310
332
|
|
311
|
-
#
|
312
|
-
|
333
|
+
# Add it to the array and trigger any watches or on events.
|
334
|
+
reactive_array_append(model)
|
313
335
|
|
314
|
-
|
315
|
-
|
336
|
+
@persistor.added(model, @array.size - 1)
|
337
|
+
end.then do
|
338
|
+
nil.then do
|
339
|
+
# Validate and save
|
340
|
+
model.run_changed
|
341
|
+
end.then do
|
342
|
+
# Mark the model as not new
|
343
|
+
model.instance_variable_set('@new', false)
|
344
|
+
|
345
|
+
# Mark the model as loaded
|
346
|
+
model.change_state_to(:loaded_state, :loaded)
|
347
|
+
|
348
|
+
end.fail do |err|
|
349
|
+
# remove from the collection because it failed to save on the server
|
350
|
+
# we don't need to call delete on the server.
|
351
|
+
index = @array.index(model)
|
352
|
+
delete_at(index, true)
|
353
|
+
|
354
|
+
# remove from the id list
|
355
|
+
@persistor.try(:remove_tracking_id, model)
|
356
|
+
|
357
|
+
# re-raise, err might not be an Error object, so we use a rejected promise to re-raise
|
358
|
+
Promise.new.reject(err)
|
359
|
+
end
|
316
360
|
end
|
317
361
|
else
|
318
362
|
promise = nil.then do
|
@@ -337,7 +381,7 @@ module Volt
|
|
337
381
|
end
|
338
382
|
|
339
383
|
# We need to setup the proxy methods below where they are defined.
|
340
|
-
proxy_with_load :[], :size, :last, :reverse, :all, :to_a, :empty?
|
384
|
+
proxy_with_load :[], :size, :last, :reverse, :all, :to_a, :empty?, :present?, :blank?
|
341
385
|
|
342
386
|
end
|
343
387
|
end
|
@@ -36,13 +36,16 @@ module FieldHelpers
|
|
36
36
|
module ClassMethods
|
37
37
|
# field lets you declare your fields instead of using the underscore syntax.
|
38
38
|
# An optional class restriction can be passed in.
|
39
|
-
def field(name, klass = nil,
|
39
|
+
def field(name, klass = nil, options = {})
|
40
40
|
if klass && !VALID_FIELD_CLASSES.include?(klass)
|
41
41
|
klass_names = VALID_FIELD_CLASSES.map(&:to_s).join(', ')
|
42
42
|
msg = "valid field types is currently limited to #{klass_names}"
|
43
43
|
fail FieldHelpers::InvalidFieldClass, msg
|
44
44
|
end
|
45
45
|
|
46
|
+
self.fields_data ||= {}
|
47
|
+
self.fields_data[name] = [klass, options]
|
48
|
+
|
46
49
|
if klass
|
47
50
|
# Add type validation, execpt for String, since anything can be a string.
|
48
51
|
unless klass == String
|
@@ -69,6 +72,7 @@ module FieldHelpers
|
|
69
72
|
end
|
70
73
|
|
71
74
|
def self.included(base)
|
75
|
+
base.class_attribute :fields_data
|
72
76
|
base.send :extend, ClassMethods
|
73
77
|
end
|
74
78
|
end
|
@@ -106,41 +106,37 @@ module Volt
|
|
106
106
|
!owner_id.nil? && owner_id == Volt.current_user_id
|
107
107
|
end
|
108
108
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
def can_read?
|
116
|
-
action_allowed?(:read)
|
117
|
-
end
|
118
|
-
|
119
|
-
def can_create?
|
120
|
-
action_allowed?(:create)
|
109
|
+
[:create, :update, :read, :delete].each do |action|
|
110
|
+
# Each can_action? (can_delete? for example) returns a promise that
|
111
|
+
# resolves to true or false if the user
|
112
|
+
define_method(:"can_#{action}?") do
|
113
|
+
action_allowed?(action)
|
114
|
+
end
|
121
115
|
end
|
122
116
|
|
123
117
|
# Checks if any denies are in place for an action (read or delete)
|
124
118
|
def action_allowed?(action_name)
|
125
119
|
# TODO: this does some unnecessary work
|
126
|
-
compute_allow_and_deny(action_name)
|
120
|
+
compute_allow_and_deny(action_name).then do
|
127
121
|
|
128
|
-
|
122
|
+
deny = @__deny_fields == true || (@__deny_fields && @__deny_fields.size > 0)
|
129
123
|
|
130
|
-
|
124
|
+
clear_allow_and_deny
|
131
125
|
|
132
|
-
|
126
|
+
!deny
|
127
|
+
end
|
133
128
|
end
|
134
129
|
|
135
130
|
# Return the list of allowed fields
|
136
131
|
def allow_and_deny_fields(action_name)
|
137
|
-
compute_allow_and_deny(action_name)
|
132
|
+
compute_allow_and_deny(action_name).then do
|
138
133
|
|
139
|
-
|
134
|
+
result = [@__allow_fields, @__deny_fields]
|
140
135
|
|
141
|
-
|
136
|
+
clear_allow_and_deny
|
142
137
|
|
143
|
-
|
138
|
+
result
|
139
|
+
end
|
144
140
|
end
|
145
141
|
|
146
142
|
# Filter fields returns the attributes with any denied or not allowed fields
|
@@ -149,70 +145,80 @@ module Volt
|
|
149
145
|
# Run with Volt.as_user(...) to change the user
|
150
146
|
def filtered_attributes
|
151
147
|
# Run the read permission check
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
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
|
148
|
+
allow_and_deny_fields(:read).then do |allow, deny|
|
149
|
+
|
150
|
+
result = nil
|
151
|
+
|
152
|
+
if allow && allow != true && allow.size > 0
|
153
|
+
# always keep id
|
154
|
+
allow << :id
|
155
|
+
|
156
|
+
# Only keep fields in the allow list
|
157
|
+
result = @attributes.select { |key| allow.include?(key) }
|
158
|
+
elsif deny == true
|
159
|
+
# Only keep id
|
160
|
+
# TODO: Should this be a full reject?
|
161
|
+
result = @attributes.reject { |key| key != :id }
|
162
|
+
elsif deny && deny.size > 0
|
163
|
+
# Reject any in the deny list
|
164
|
+
result = @attributes.reject { |key| deny.include?(key) }
|
165
|
+
else
|
166
|
+
result = @attributes
|
177
167
|
end
|
178
168
|
|
179
|
-
|
180
|
-
|
169
|
+
# Deeply filter any nested models
|
170
|
+
result.then do |res|
|
171
|
+
keys = []
|
172
|
+
values = []
|
173
|
+
res.each do |key, value|
|
174
|
+
if value.is_a?(Model)
|
175
|
+
value = value.filtered_attributes
|
176
|
+
end
|
177
|
+
keys << key
|
178
|
+
values << value
|
179
|
+
end
|
180
|
+
|
181
|
+
Promise.when(*values).then do |values|
|
182
|
+
keys.zip(values).to_h
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
181
186
|
end
|
182
187
|
|
183
188
|
private
|
184
189
|
|
185
190
|
def run_permissions(action_name = nil)
|
186
|
-
compute_allow_and_deny(action_name)
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
191
|
+
compute_allow_and_deny(action_name).then do
|
192
|
+
|
193
|
+
errors = {}
|
194
|
+
|
195
|
+
if @__allow_fields == true
|
196
|
+
# Allow all fields
|
197
|
+
elsif @__allow_fields && @__allow_fields.size > 0
|
198
|
+
# Deny all not specified in the allow list
|
199
|
+
changed_attributes.keys.each do |field_name|
|
200
|
+
unless @__allow_fields.include?(field_name)
|
201
|
+
add_error_if_changed(errors, field_name)
|
202
|
+
end
|
197
203
|
end
|
198
204
|
end
|
199
|
-
end
|
200
205
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
206
|
+
if @__deny_fields == true
|
207
|
+
# Don't allow any field changes
|
208
|
+
changed_attributes.keys.each do |field_name|
|
209
|
+
add_error_if_changed(errors, field_name)
|
210
|
+
end
|
211
|
+
elsif @__deny_fields
|
212
|
+
# Allow all except the denied
|
213
|
+
@__deny_fields.each do |field_name|
|
214
|
+
add_error_if_changed(errors, field_name) if changed?(field_name)
|
215
|
+
end
|
210
216
|
end
|
211
|
-
end
|
212
217
|
|
213
|
-
|
218
|
+
clear_allow_and_deny
|
214
219
|
|
215
|
-
|
220
|
+
errors
|
221
|
+
end
|
216
222
|
end
|
217
223
|
|
218
224
|
def clear_allow_and_deny
|
@@ -235,10 +241,13 @@ module Volt
|
|
235
241
|
# Run each of the permission blocks for this action
|
236
242
|
permissions = self.class.__permissions__
|
237
243
|
if permissions && (blocks = permissions[action_name])
|
238
|
-
blocks.
|
244
|
+
results = blocks.map do |block|
|
239
245
|
# Call the block, pass the action name
|
240
246
|
instance_exec(action_name, &block)
|
241
247
|
end
|
248
|
+
|
249
|
+
# Wait for any promises returned
|
250
|
+
Promise.when(*results)
|
242
251
|
end
|
243
252
|
end
|
244
253
|
|