volt 0.8.27.beta6 → 0.8.27.beta7
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 +16 -3
- data/VERSION +1 -1
- data/app/volt/models/user.rb +1 -1
- data/app/volt/tasks/query_tasks.rb +2 -2
- data/app/volt/tasks/store_tasks.rb +14 -4
- data/app/volt/tasks/user_tasks.rb +1 -1
- data/lib/volt/controllers/http_controller.rb +60 -0
- data/lib/volt/controllers/model_controller.rb +5 -1
- data/lib/volt/extra_core/string.rb +6 -0
- data/lib/volt/models/array_model.rb +6 -2
- data/lib/volt/models/associations.rb +1 -1
- data/lib/volt/models/buffer.rb +14 -3
- data/lib/volt/models/model.rb +28 -60
- data/lib/volt/models/permissions.rb +4 -4
- data/lib/volt/reactive/computation.rb +15 -15
- data/lib/volt/reactive/reactive_array.rb +1 -0
- data/lib/volt/router/routes.rb +67 -27
- data/lib/volt/server.rb +37 -6
- data/lib/volt/server/component_templates.rb +2 -2
- data/lib/volt/server/rack/http_request.rb +50 -0
- data/lib/volt/server/rack/http_resource.rb +41 -0
- data/lib/volt/server/rack/http_response_header.rb +33 -0
- data/lib/volt/server/rack/http_response_renderer.rb +41 -0
- data/lib/volt/spec/setup.rb +4 -2
- data/lib/volt/tasks/dispatcher.rb +7 -7
- data/lib/volt/tasks/task_handler.rb +1 -1
- data/lib/volt/volt/users.rb +12 -6
- data/spec/apps/kitchen_sink/app/main/config/routes.rb +18 -10
- data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +2 -2
- data/spec/apps/kitchen_sink/app/main/controllers/server/simple_http_controller.rb +15 -0
- data/spec/apps/kitchen_sink/app/main/controllers/upload_controller.rb +22 -0
- data/spec/apps/kitchen_sink/app/main/views/main/yield.html +2 -2
- data/spec/apps/kitchen_sink/app/main/views/upload/index.html +15 -0
- data/spec/controllers/http_controller_spec.rb +130 -0
- data/spec/extra_core/string_transformation_test_cases.rb +8 -0
- data/spec/extra_core/string_transformations_spec.rb +12 -0
- data/spec/integration/http_endpoints_spec.rb +29 -0
- data/spec/integration/user_spec.rb +42 -42
- data/spec/models/associations_spec.rb +4 -4
- data/spec/models/buffer_spec.rb +15 -0
- data/spec/models/model_spec.rb +70 -25
- data/spec/models/model_state_spec.rb +1 -1
- data/spec/models/permissions_spec.rb +64 -2
- data/spec/models/persistors/params_spec.rb +8 -8
- data/spec/models/persistors/store_spec.rb +1 -1
- data/spec/models/user_validation_spec.rb +1 -1
- data/spec/router/routes_spec.rb +111 -43
- data/spec/server/rack/http_request_spec.rb +50 -0
- data/spec/server/rack/http_resource_spec.rb +59 -0
- data/spec/server/rack/http_response_header_spec.rb +34 -0
- data/spec/server/rack/http_response_renderer_spec.rb +33 -0
- data/spec/tasks/dispatcher_spec.rb +2 -2
- data/templates/component/config/routes.rb +2 -2
- data/templates/project/Gemfile.tt +3 -5
- data/templates/project/app/main/config/routes.rb +4 -4
- data/volt.gemspec +2 -2
- metadata +33 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a8f7ccdc9801cf342901b77cf83b2091af34975f
|
4
|
+
data.tar.gz: a4f0dd2b28f09f2423fa602eb45c9935d422840c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94607eb5080e4809be8a505e2a6e0c2602a887346de86c335e7177b1230ed75048091a9d1751d1b3d30e1960bb242b385966c58eb1b13bd52f03a05b253f3069
|
7
|
+
data.tar.gz: bb4d64bc6781e697e646f53054c305499fcd4434e10c8bfde661c46fb0fef31a3a0bda791818c783bc5631aacfadd156486baddb0537a4868dd597056f91708c
|
data/CHANGELOG.md
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
## 0.8.27 - WIP
|
4
4
|
### Added
|
5
|
-
-
|
5
|
+
- added has_many and belongs_to on models. See docs.
|
6
|
+
- you can now serve http/rest from Volt. Thanks to @jfahrer for his great work. Docs coming soon.
|
6
7
|
- fixed generated component code
|
7
8
|
- added .order for sorting on the data store (since .sort is a ruby Enum method)
|
8
|
-
-
|
9
|
-
- added .fetch and .fetch_first for waiting on store model loads
|
9
|
+
- calling .then on ArrayModels has been changed to calling .fetch and .fetch_first. These both return a promise, and take an optional block
|
10
10
|
- added .sync for synchronusly waiting on promises on the server only
|
11
11
|
- added the ability to pass content into tags: (https://github.com/voltrb/docs/blob/master/en/docs/yield_binding.md)
|
12
12
|
- the {action}_remove method had been changed to before_{action}_remove and after_{action}_remove to provide more hooks and a clearer understanding of when it is happening.
|
@@ -18,9 +18,22 @@
|
|
18
18
|
- refactored TemplateBinding, moved code into ViewLookupForPath (SRP)
|
19
19
|
- reserved fields now get a warning in models
|
20
20
|
- bindings will now resolve any values that are promises. (currently only content and attribute, if, each, and template coming soon)
|
21
|
+
- ```store``` is now available inside of specs. If it is accessed in a spec, the database will be cleaned after the spec.
|
22
|
+
- ```the_page``` is a shortcut to the page collection inside of specs. (Unfortunately, ```page``` is used by capybara, so for now we're using ```the_page```, we'll find a better solution in the future.)
|
21
23
|
|
22
24
|
### Changed
|
25
|
+
- model attributes no longer return NilModels. Instead they just return nil. You can however add an ! to the end to "expand" the model to an empty model.
|
26
|
+
|
27
|
+
```page._new_todo # => now returns nil```
|
28
|
+
|
29
|
+
```page._new_todo! # => returns an empty model```
|
30
|
+
|
31
|
+
So if you wanted to use a property on ```_new_todo``` without initializing ```_new_todo```, you could add the ! to the lookup.
|
32
|
+
- Volt.user has been renamed to Volt.current_user so its clearer what it returns
|
33
|
+
- _'s are no longer required for route constraints (so just use ```controller: 'main', action: 'index'``` now)
|
23
34
|
- the underlying way queries are normalized and passed to the server has changed (no external api changes)
|
35
|
+
- changed .find to .where to not conflict with ruby Enum's .find
|
36
|
+
- Volt::TaskHandler is now Volt::Task
|
24
37
|
|
25
38
|
## 0.8.24 - 2014-12-05
|
26
39
|
### Added
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.8.27.
|
1
|
+
0.8.27.beta7
|
data/app/volt/models/user.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require_relative 'live_query/data_store'
|
2
2
|
require_relative 'live_query/live_query_pool'
|
3
3
|
|
4
|
-
class QueryTasks < Volt::
|
4
|
+
class QueryTasks < Volt::Task
|
5
5
|
@@live_query_pool = LiveQueryPool.new(DataStore.new)
|
6
6
|
@@channel_live_queries = {}
|
7
7
|
|
@@ -23,7 +23,7 @@ class QueryTasks < Volt::TaskHandler
|
|
23
23
|
# For requests from the client (with @channel), we track the channel
|
24
24
|
# so we can send the results back. Server side requests don't stay live,
|
25
25
|
# they simply return to :dirty once the query is issued.
|
26
|
-
@channel.user_id = Volt.
|
26
|
+
@channel.user_id = Volt.current_user_id
|
27
27
|
|
28
28
|
live_query.add_channel(@channel)
|
29
29
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'mongo'
|
2
2
|
require 'volt/models'
|
3
3
|
|
4
|
-
class StoreTasks < Volt::
|
4
|
+
class StoreTasks < Volt::Task
|
5
5
|
def initialize(channel = nil, dispatcher = nil)
|
6
6
|
@channel = channel
|
7
7
|
@dispatcher = dispatcher
|
@@ -35,8 +35,12 @@ class StoreTasks < Volt::TaskHandler
|
|
35
35
|
def save(collection, path, data)
|
36
36
|
data = data.symbolize_keys
|
37
37
|
promise = nil
|
38
|
-
|
39
|
-
|
38
|
+
|
39
|
+
# Don't check the permissions when we load the model, since we want all fields
|
40
|
+
Volt.skip_permissions do
|
41
|
+
Volt::Model.no_validate do
|
42
|
+
promise = load_model(collection, path, data)
|
43
|
+
end
|
40
44
|
end
|
41
45
|
|
42
46
|
# On the backend, the promise is resolved before its returned, so we can
|
@@ -61,7 +65,13 @@ class StoreTasks < Volt::TaskHandler
|
|
61
65
|
|
62
66
|
def delete(collection, id)
|
63
67
|
# Load the model, then call .destroy on it
|
64
|
-
|
68
|
+
query = nil
|
69
|
+
|
70
|
+
Volt.skip_permissions do
|
71
|
+
query = store.send(:"_#{collection}").where(_id: id)
|
72
|
+
end
|
73
|
+
|
74
|
+
query.fetch_first do |model|
|
65
75
|
if model
|
66
76
|
if model.can_delete?
|
67
77
|
db[collection].remove('_id' => id)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'volt/server/rack/http_response_header'
|
2
|
+
require 'volt/server/rack/http_response_renderer'
|
3
|
+
|
4
|
+
module Volt
|
5
|
+
# Allow you to create controllers that act as http endpoints
|
6
|
+
class HttpController
|
7
|
+
attr_accessor :response_body
|
8
|
+
attr_reader :params, :response_headers, :request
|
9
|
+
|
10
|
+
# Initialzed with the params parsed from the route and the HttpRequest
|
11
|
+
def initialize(params, request)
|
12
|
+
@response_headers = HttpResponseHeader.new
|
13
|
+
@response_body = []
|
14
|
+
@request = request
|
15
|
+
@params = params.symbolize_keys.merge(request.params)
|
16
|
+
end
|
17
|
+
|
18
|
+
def perform(action)
|
19
|
+
# TODO: before actions
|
20
|
+
send(action.to_sym)
|
21
|
+
# TODO: after actions / around actions
|
22
|
+
respond
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def store
|
28
|
+
$page.store
|
29
|
+
end
|
30
|
+
|
31
|
+
def head(status, additional_headers = {})
|
32
|
+
@response_status = status
|
33
|
+
response_headers.merge!(additional_headers)
|
34
|
+
end
|
35
|
+
|
36
|
+
def redirect_to(target, status = :found)
|
37
|
+
head(status, location: target)
|
38
|
+
end
|
39
|
+
|
40
|
+
def render(content)
|
41
|
+
status = content.delete(:status) || :ok
|
42
|
+
body, additional_headers = HttpResponseRenderer.new.render(content)
|
43
|
+
head(status, additional_headers)
|
44
|
+
response_body << body
|
45
|
+
end
|
46
|
+
|
47
|
+
def respond
|
48
|
+
Rack::Response.new(response_body, response_status, response_headers)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Get the http status code as integer
|
52
|
+
def response_status
|
53
|
+
if @response_status.is_a?(Symbol)
|
54
|
+
Rack::Utils::SYMBOL_TO_STATUS_CODE[@response_status]
|
55
|
+
else
|
56
|
+
@response_status.try(:to_i) || 200
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -108,7 +108,11 @@ module Volt
|
|
108
108
|
|
109
109
|
# Change the url params, similar to redirecting to a new url
|
110
110
|
def go(url)
|
111
|
-
|
111
|
+
# We might be in the rendering loop, so wait until the next tick before
|
112
|
+
# we change the url
|
113
|
+
Timers.next_tick do
|
114
|
+
self.url.parse(url)
|
115
|
+
end
|
112
116
|
end
|
113
117
|
|
114
118
|
def page
|
@@ -35,6 +35,12 @@ class String
|
|
35
35
|
gsub('_', ' ').split(' ').map(&:capitalize).join(' ')
|
36
36
|
end
|
37
37
|
|
38
|
+
def headerize
|
39
|
+
split(/[_-]/)
|
40
|
+
.map { |new_str| new_str[0].capitalize + new_str[1..-1] }
|
41
|
+
.join('-')
|
42
|
+
end
|
43
|
+
|
38
44
|
def plural?
|
39
45
|
# TODO: Temp implementation
|
40
46
|
pluralize == self
|
@@ -82,7 +82,10 @@ module Volt
|
|
82
82
|
if @persistor
|
83
83
|
promise = @persistor.added(model, @array.size - 1)
|
84
84
|
if promise && promise.is_a?(Promise)
|
85
|
-
return promise.
|
85
|
+
return promise.then do
|
86
|
+
# return the model
|
87
|
+
model
|
88
|
+
end.fail do |err|
|
86
89
|
# remove from the collection because it failed to save on the server
|
87
90
|
@array.delete(model)
|
88
91
|
|
@@ -95,7 +98,8 @@ module Volt
|
|
95
98
|
end
|
96
99
|
end
|
97
100
|
else
|
98
|
-
|
101
|
+
# Return this model
|
102
|
+
model
|
99
103
|
end
|
100
104
|
end
|
101
105
|
|
@@ -21,7 +21,7 @@ module Volt
|
|
21
21
|
# The key will be "{this class name}_id"
|
22
22
|
remote_key_name ||= :"#{path[-2].singularize}_id"
|
23
23
|
|
24
|
-
root.send(:"_#{method_name.pluralize}").where(remote_key_name => id)
|
24
|
+
root.send(:"_#{method_name.pluralize}!").where(remote_key_name => id)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
data/lib/volt/models/buffer.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
module Volt
|
2
2
|
module Buffer
|
3
|
-
|
3
|
+
# Save saves the contents of a buffer to the save_to location. If the buffer is new, it will create a new
|
4
|
+
# model to save to. Otherwise it will be an existing model. Save returns a promise that may fail with
|
5
|
+
# validation errors from the server (or the client). You can also pass a block as a shortcut to calling
|
6
|
+
# ```.save!.then do```
|
7
|
+
def save!(&block)
|
4
8
|
# TODO: this shouldn't need to be run, but if no attributes are assigned, then
|
5
9
|
# if needs to be run. Maybe there's a better way to handle it.
|
6
10
|
validate!
|
@@ -8,6 +12,8 @@ module Volt
|
|
8
12
|
# Get errors from validate
|
9
13
|
errors = self.errors.to_h
|
10
14
|
|
15
|
+
result = nil
|
16
|
+
|
11
17
|
if errors.size == 0
|
12
18
|
save_to = options[:save_to]
|
13
19
|
if save_to
|
@@ -19,7 +25,7 @@ module Volt
|
|
19
25
|
promise = save_to.assign_attributes(attributes)
|
20
26
|
end
|
21
27
|
|
22
|
-
|
28
|
+
result = promise.then do |new_model|
|
23
29
|
# The main model saved, so mark the buffer as not new
|
24
30
|
@new = false
|
25
31
|
|
@@ -42,8 +48,13 @@ module Volt
|
|
42
48
|
end
|
43
49
|
else
|
44
50
|
# Some errors, mark all fields
|
45
|
-
promise_for_errors(errors)
|
51
|
+
result = promise_for_errors(errors)
|
46
52
|
end
|
53
|
+
|
54
|
+
# If passed a block, call then on it with the block.
|
55
|
+
result = result.then(&block) if block
|
56
|
+
|
57
|
+
return result
|
47
58
|
end
|
48
59
|
|
49
60
|
# When errors come in, we mark all fields and return a rejected promise.
|
data/lib/volt/models/model.rb
CHANGED
@@ -188,7 +188,12 @@ module Volt
|
|
188
188
|
# Assigning an attribute without the =
|
189
189
|
set(method_name[0..-2], args[0], &block)
|
190
190
|
else
|
191
|
-
|
191
|
+
# If the method has an ! on the end, then we assign an empty
|
192
|
+
# collection if no result exists already.
|
193
|
+
expand = (method_name[-1] == '!')
|
194
|
+
method_name = method_name[0..-2] if expand
|
195
|
+
|
196
|
+
get(method_name, expand)
|
192
197
|
end
|
193
198
|
else
|
194
199
|
# Call on parent
|
@@ -198,7 +203,6 @@ module Volt
|
|
198
203
|
|
199
204
|
# Do the assignment to a model and trigger a changed event
|
200
205
|
def set(attribute_name, value, &block)
|
201
|
-
self.expand!
|
202
206
|
# Assign, without the =
|
203
207
|
attribute_name = attribute_name.to_sym
|
204
208
|
|
@@ -227,13 +231,15 @@ module Volt
|
|
227
231
|
# Save the changes
|
228
232
|
run_changed(attribute_name) unless Volt.in_mode?(:no_change_tracking)
|
229
233
|
end
|
234
|
+
|
235
|
+
new_value
|
230
236
|
end
|
231
237
|
|
232
238
|
# When reading an attribute, we need to handle reading on:
|
233
239
|
# 1) a nil model, which returns a wrapped error
|
234
240
|
# 2) reading directly from attributes
|
235
241
|
# 3) trying to read a key that doesn't exist.
|
236
|
-
def get(attr_name)
|
242
|
+
def get(attr_name, expand=false)
|
237
243
|
# Reading an attribute, we may get back a nil model.
|
238
244
|
attr_name = attr_name.to_sym
|
239
245
|
|
@@ -242,31 +248,29 @@ module Volt
|
|
242
248
|
# Track that something is listening
|
243
249
|
@root_dep.depend
|
244
250
|
|
251
|
+
# Track dependency
|
252
|
+
@deps.depend(attr_name)
|
253
|
+
|
245
254
|
# See if the value is in attributes
|
246
255
|
if @attributes && @attributes.key?(attr_name)
|
247
|
-
# Track dependency
|
248
|
-
@deps.depend(attr_name)
|
249
|
-
|
250
256
|
return @attributes[attr_name]
|
251
257
|
else
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
258
|
+
# If we're expanding, or the get is for a collection, in which
|
259
|
+
# case we always expand.
|
260
|
+
if expand || attr_name.plural?
|
261
|
+
new_value = read_new_model(attr_name)
|
262
|
+
|
263
|
+
# A value was generated, store it
|
264
|
+
if new_value
|
265
|
+
# Assign directly. Since this is the first time
|
266
|
+
# we're loading, we can just assign.
|
267
|
+
set(attr_name, new_value)
|
268
|
+
end
|
269
|
+
|
270
|
+
return new_value
|
263
271
|
else
|
264
|
-
|
272
|
+
return nil
|
265
273
|
end
|
266
|
-
|
267
|
-
# Depend on attribute
|
268
|
-
@deps.depend(attr_name)
|
269
|
-
return new_model
|
270
274
|
end
|
271
275
|
end
|
272
276
|
|
@@ -280,10 +284,11 @@ module Volt
|
|
280
284
|
return @persistor.read_new_model(method_name)
|
281
285
|
else
|
282
286
|
opts = @options.merge(parent: self, path: path + [method_name])
|
287
|
+
|
283
288
|
if method_name.plural?
|
284
289
|
return new_array_model([], opts)
|
285
290
|
else
|
286
|
-
return new_model(
|
291
|
+
return new_model({}, opts)
|
287
292
|
end
|
288
293
|
end
|
289
294
|
end
|
@@ -300,43 +305,6 @@ module Volt
|
|
300
305
|
ArrayModel.new(attributes, options)
|
301
306
|
end
|
302
307
|
|
303
|
-
# If this model is nil, it makes it into a hash model, then
|
304
|
-
# sets it up to track from the parent.
|
305
|
-
def expand!
|
306
|
-
if attributes.nil?
|
307
|
-
@attributes = {}
|
308
|
-
if @parent
|
309
|
-
@parent.expand!
|
310
|
-
|
311
|
-
@parent.send(:"_#{@path.last}=", self)
|
312
|
-
end
|
313
|
-
end
|
314
|
-
end
|
315
|
-
|
316
|
-
# Initialize an empty array and append to it
|
317
|
-
def <<(value)
|
318
|
-
if @parent
|
319
|
-
@parent.expand!
|
320
|
-
else
|
321
|
-
fail 'Model data should be stored in sub collections.'
|
322
|
-
end
|
323
|
-
|
324
|
-
# Grab the last section of the path, so we can do the assign on the parent
|
325
|
-
path = @path.last
|
326
|
-
result = @parent.send(path)
|
327
|
-
|
328
|
-
if result.nil?
|
329
|
-
# If this isn't a model yet, instantiate it
|
330
|
-
@parent.send(:"#{path}=", new_array_model([], @options))
|
331
|
-
result = @parent.send(path)
|
332
|
-
end
|
333
|
-
|
334
|
-
# Add the new item
|
335
|
-
result << value
|
336
|
-
|
337
|
-
nil
|
338
|
-
end
|
339
|
-
|
340
308
|
def inspect
|
341
309
|
Computation.run_without_tracking do
|
342
310
|
str = "<#{self.class}:#{object_id}"
|
@@ -4,7 +4,7 @@ module Volt
|
|
4
4
|
# The permissions module provides helpers for working with Volt permissions.
|
5
5
|
module Permissions
|
6
6
|
module ClassMethods
|
7
|
-
# Own by user requires a logged in user (Volt.
|
7
|
+
# Own by user requires a logged in user (Volt.current_user) to save a model. If
|
8
8
|
# the user is not logged in, an validation error will occur. Once created
|
9
9
|
# the user can not be changed.
|
10
10
|
#
|
@@ -13,7 +13,7 @@ module Volt
|
|
13
13
|
# When the model is created, assign it the user_id (if the user is logged in)
|
14
14
|
on(:new) do
|
15
15
|
# Only assign the user_id if there isn't already one and the user is logged in.
|
16
|
-
if _user_id.nil? && !(user_id = Volt.
|
16
|
+
if _user_id.nil? && !(user_id = Volt.current_user_id).nil?
|
17
17
|
send(:"_#{key}=", user_id)
|
18
18
|
end
|
19
19
|
end
|
@@ -102,13 +102,13 @@ module Volt
|
|
102
102
|
end
|
103
103
|
|
104
104
|
# owner? can be called on a model to check if the currently logged
|
105
|
-
# in user (```Volt.
|
105
|
+
# in user (```Volt.current_user```) is the owner of this instance.
|
106
106
|
#
|
107
107
|
# @param key [Symbol] the name of the attribute where the user_id is stored
|
108
108
|
def owner?(key=:user_id)
|
109
109
|
# Lookup the original user_id
|
110
110
|
owner_id = was(key) || send(:"_#{key}")
|
111
|
-
owner_id != nil && owner_id == Volt.
|
111
|
+
owner_id != nil && owner_id == Volt.current_user_id
|
112
112
|
end
|
113
113
|
|
114
114
|
# Returns boolean if the model can be deleted
|