volt 0.8.27.beta6 → 0.8.27.beta7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|