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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -3
  3. data/VERSION +1 -1
  4. data/app/volt/models/user.rb +1 -1
  5. data/app/volt/tasks/query_tasks.rb +2 -2
  6. data/app/volt/tasks/store_tasks.rb +14 -4
  7. data/app/volt/tasks/user_tasks.rb +1 -1
  8. data/lib/volt/controllers/http_controller.rb +60 -0
  9. data/lib/volt/controllers/model_controller.rb +5 -1
  10. data/lib/volt/extra_core/string.rb +6 -0
  11. data/lib/volt/models/array_model.rb +6 -2
  12. data/lib/volt/models/associations.rb +1 -1
  13. data/lib/volt/models/buffer.rb +14 -3
  14. data/lib/volt/models/model.rb +28 -60
  15. data/lib/volt/models/permissions.rb +4 -4
  16. data/lib/volt/reactive/computation.rb +15 -15
  17. data/lib/volt/reactive/reactive_array.rb +1 -0
  18. data/lib/volt/router/routes.rb +67 -27
  19. data/lib/volt/server.rb +37 -6
  20. data/lib/volt/server/component_templates.rb +2 -2
  21. data/lib/volt/server/rack/http_request.rb +50 -0
  22. data/lib/volt/server/rack/http_resource.rb +41 -0
  23. data/lib/volt/server/rack/http_response_header.rb +33 -0
  24. data/lib/volt/server/rack/http_response_renderer.rb +41 -0
  25. data/lib/volt/spec/setup.rb +4 -2
  26. data/lib/volt/tasks/dispatcher.rb +7 -7
  27. data/lib/volt/tasks/task_handler.rb +1 -1
  28. data/lib/volt/volt/users.rb +12 -6
  29. data/spec/apps/kitchen_sink/app/main/config/routes.rb +18 -10
  30. data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +2 -2
  31. data/spec/apps/kitchen_sink/app/main/controllers/server/simple_http_controller.rb +15 -0
  32. data/spec/apps/kitchen_sink/app/main/controllers/upload_controller.rb +22 -0
  33. data/spec/apps/kitchen_sink/app/main/views/main/yield.html +2 -2
  34. data/spec/apps/kitchen_sink/app/main/views/upload/index.html +15 -0
  35. data/spec/controllers/http_controller_spec.rb +130 -0
  36. data/spec/extra_core/string_transformation_test_cases.rb +8 -0
  37. data/spec/extra_core/string_transformations_spec.rb +12 -0
  38. data/spec/integration/http_endpoints_spec.rb +29 -0
  39. data/spec/integration/user_spec.rb +42 -42
  40. data/spec/models/associations_spec.rb +4 -4
  41. data/spec/models/buffer_spec.rb +15 -0
  42. data/spec/models/model_spec.rb +70 -25
  43. data/spec/models/model_state_spec.rb +1 -1
  44. data/spec/models/permissions_spec.rb +64 -2
  45. data/spec/models/persistors/params_spec.rb +8 -8
  46. data/spec/models/persistors/store_spec.rb +1 -1
  47. data/spec/models/user_validation_spec.rb +1 -1
  48. data/spec/router/routes_spec.rb +111 -43
  49. data/spec/server/rack/http_request_spec.rb +50 -0
  50. data/spec/server/rack/http_resource_spec.rb +59 -0
  51. data/spec/server/rack/http_response_header_spec.rb +34 -0
  52. data/spec/server/rack/http_response_renderer_spec.rb +33 -0
  53. data/spec/tasks/dispatcher_spec.rb +2 -2
  54. data/templates/component/config/routes.rb +2 -2
  55. data/templates/project/Gemfile.tt +3 -5
  56. data/templates/project/app/main/config/routes.rb +4 -4
  57. data/volt.gemspec +2 -2
  58. metadata +33 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 664d87e842c6c03b483345d4771a71d72ad1567e
4
- data.tar.gz: 8e82d85deddbc176c6cda90317b5a005f63f918d
3
+ metadata.gz: a8f7ccdc9801cf342901b77cf83b2091af34975f
4
+ data.tar.gz: a4f0dd2b28f09f2423fa602eb45c9935d422840c
5
5
  SHA512:
6
- metadata.gz: a8e1ffac496bf1ead1564de04a4dac9a48ccbc68776d5db3fa0b9bb8ed2fcb7b10a31c9e5869f1f521f6b0d0796c37937a041a4efbe398d674e0884cbf93e3c2
7
- data.tar.gz: 5e54e036809c3d61f75de5a80f01658d46ef572aacc0193c45498a97b92c41dc665e2b761dcc848f7206a21240b18698557cc2df90be43cdf36b39b4594cce9c
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
- - _'s are no longer required for route constraints (so just use ```controller: 'main', action: 'index'``` now)
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
- - changed .find to .where to not conflict with ruby Enum's .find
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.beta6
1
+ 0.8.27.beta7
@@ -21,7 +21,7 @@ module Volt
21
21
  deny :hashed_password
22
22
 
23
23
  # Deny all if this isn't the owner
24
- deny if !_id == Volt.user_id && !new?
24
+ deny if !_id == Volt.current_user_id && !new?
25
25
  end
26
26
 
27
27
  if RUBY_PLATFORM == 'opal'
@@ -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::TaskHandler
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.user_id
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::TaskHandler
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
- Volt::Model.no_validate do
39
- promise = load_model(collection, path, data)
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
- store.send(:"_#{collection}").where(_id: id).fetch_first do |model|
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)
@@ -1,4 +1,4 @@
1
- class UserTasks < Volt::TaskHandler
1
+ class UserTasks < Volt::Task
2
2
  # Login a user, takes a login and password. Login can be either a username
3
3
  # or an e-mail based on Volt.config.public.auth.use_username
4
4
  def login(login, password)
@@ -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
- self.url.parse(url)
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.fail do |err|
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
- nil
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
@@ -1,6 +1,10 @@
1
1
  module Volt
2
2
  module Buffer
3
- def save!
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
- return promise.then do |new_model|
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.
@@ -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
- get(method_name)
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
- new_model = read_new_model(attr_name)
253
- @attributes ||= {}
254
- @attributes[attr_name] = new_model
255
-
256
- # Trigger size change
257
- # TODO: We can probably improve Computations to just make this work
258
- # without the delay
259
- if Volt.in_browser?
260
- `setImmediate(function() {`
261
- @size_dep.changed!
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
- @size_dep.changed!
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(nil, opts)
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.user) to save a model. If
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.user_id).nil?
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.user```) is the owner of this instance.
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.user_id
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