volt 0.9.3.pre2 → 0.9.3.pre3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/app/volt/tasks/query_tasks.rb +0 -7
  4. data/app/volt/tasks/store_tasks.rb +0 -6
  5. data/docs/UPGRADE_GUIDE.md +2 -0
  6. data/lib/volt/cli/asset_compile.rb +2 -2
  7. data/lib/volt/cli/console.rb +21 -0
  8. data/lib/volt/config.rb +0 -10
  9. data/lib/volt/controllers/collection_helpers.rb +18 -0
  10. data/lib/volt/controllers/model_controller.rb +2 -12
  11. data/lib/volt/extra_core/object.rb +19 -0
  12. data/lib/volt/models.rb +14 -9
  13. data/lib/volt/models/array_model.rb +62 -22
  14. data/lib/volt/models/associations.rb +16 -1
  15. data/lib/volt/models/model.rb +27 -15
  16. data/lib/volt/models/model_helpers/model_helpers.rb +29 -0
  17. data/lib/volt/models/permissions.rb +15 -4
  18. data/lib/volt/models/persistors/array_store.rb +40 -0
  19. data/lib/volt/models/persistors/model_store.rb +2 -2
  20. data/lib/volt/models/persistors/query/query_listener.rb +3 -1
  21. data/lib/volt/models/persistors/store.rb +2 -1
  22. data/lib/volt/models/root_models/root_models.rb +31 -0
  23. data/lib/volt/models/root_models/store_root.rb +36 -0
  24. data/lib/volt/models/validators/unique_validator.rb +1 -1
  25. data/lib/volt/page/bindings/each_binding.rb +56 -47
  26. data/lib/volt/page/page.rb +5 -5
  27. data/lib/volt/reactive/reactive_array.rb +9 -6
  28. data/lib/volt/server.rb +2 -2
  29. data/lib/volt/server/component_templates.rb +7 -4
  30. data/lib/volt/server/message_bus/message_encoder.rb +9 -1
  31. data/lib/volt/server/rack/component_code.rb +8 -1
  32. data/lib/volt/server/rack/index_files.rb +5 -2
  33. data/lib/volt/tasks/{task_handler.rb → task.rb} +6 -6
  34. data/lib/volt/utils/promise.rb +429 -0
  35. data/lib/volt/utils/promise_extensions.rb +79 -0
  36. data/lib/volt/version.rb +1 -1
  37. data/lib/volt/volt/app.rb +5 -2
  38. data/lib/volt/volt/server_setup/app.rb +28 -7
  39. data/spec/apps/kitchen_sink/app/main/controllers/server/simple_http_controller.rb +1 -1
  40. data/spec/apps/kitchen_sink/app/main/views/main/store.html +3 -0
  41. data/spec/extra_core/object_spec.rb +13 -0
  42. data/spec/integration/store_spec.rb +10 -0
  43. data/spec/models/associations_spec.rb +48 -26
  44. data/spec/models/model_spec.rb +23 -7
  45. data/spec/models/persistors/store_spec.rb +28 -0
  46. data/spec/models/validators/unique_validator_spec.rb +1 -1
  47. data/spec/spec_helper.rb +4 -1
  48. data/spec/utils/promise_extensions_spec.rb +42 -0
  49. data/templates/component/config/initializers/boot.rb +10 -0
  50. data/templates/{project/app → component/config/initializers/client}/.empty_directory +0 -0
  51. data/templates/component/config/initializers/server/.empty_directory +0 -0
  52. data/templates/newgem/app/newgem/config/initializers/client/.empty_directory +0 -0
  53. data/templates/newgem/app/newgem/config/initializers/server/.empty_directory +0 -0
  54. data/templates/project/Gemfile.tt +6 -2
  55. data/templates/project/app/main/config/initializers/boot.rb +10 -0
  56. data/templates/project/app/main/config/initializers/client/.empty_directory +0 -0
  57. data/templates/project/app/main/config/initializers/server/.empty_directory +0 -0
  58. data/templates/project/config/app.rb.tt +3 -0
  59. data/templates/project/config/initializers/client/.empty_directory +0 -0
  60. data/templates/project/config/initializers/server/.empty_directory +0 -0
  61. metadata +22 -5
  62. data/lib/volt/utils/promise_patch.rb +0 -70
data/lib/volt/server.rb CHANGED
@@ -10,7 +10,7 @@ require 'sprockets-sass'
10
10
 
11
11
  require 'volt'
12
12
  require 'volt/tasks/dispatcher'
13
- require 'volt/tasks/task_handler'
13
+ require 'volt/tasks/task'
14
14
  require 'volt/server/component_handler'
15
15
  require 'volt/server/rack/component_paths'
16
16
  require 'volt/server/rack/index_files'
@@ -133,7 +133,7 @@ module Volt
133
133
 
134
134
  # Serve the main html files from public, also figure out
135
135
  # which JS/CSS files to serve.
136
- @rack_app.use IndexFiles, @volt_app.component_paths, opal_files
136
+ @rack_app.use IndexFiles, @volt_app, @volt_app.component_paths, opal_files
137
137
 
138
138
  @rack_app.use HttpResource, @volt_app, @volt_app.router
139
139
 
@@ -1,5 +1,5 @@
1
1
  require 'volt/server/html_parser/view_parser'
2
- require 'volt/tasks/task_handler'
2
+ require 'volt/tasks/task'
3
3
 
4
4
  # Initialize with the path to a component and returns all the front-end
5
5
  # setup code (for controllers, models, views, and routes)
@@ -91,7 +91,7 @@ module Volt
91
91
 
92
92
  file_contents = File.read(view_path)
93
93
 
94
- template_calls = []
94
+ # template_calls = []
95
95
 
96
96
  # Process template if we have a handler for this file type
97
97
  if handler = ComponentTemplates.handler_for_extension(format)
@@ -112,7 +112,7 @@ module Volt
112
112
  binding_code = "{#{binding_code.join(', ')}}"
113
113
 
114
114
  code << "#{page_reference}.add_template(#{name.inspect}, #{template['html'].inspect}, #{binding_code})\n"
115
- template_calls << "template(#{name.inspect}, #{template['html'].inspect}, #{binding_code})"
115
+ # template_calls << "template(#{name.inspect}, #{template['html'].inspect}, #{binding_code})"
116
116
  end
117
117
  end
118
118
 
@@ -200,7 +200,10 @@ module Volt
200
200
  end
201
201
 
202
202
  def generate_initializers_code
203
- "\nrequire_tree '#{@component_path}/config/initializers/'\n"
203
+ code = "\nrequire_tree '#{@component_path}/config/initializers/'\n"
204
+ code << "require_tree '#{@component_path}/config/initializers/client'\n"
205
+
206
+ code
204
207
  end
205
208
 
206
209
  end
@@ -5,8 +5,16 @@ module Volt
5
5
  class MessageEncoder
6
6
  attr_reader :encrypted
7
7
  def initialize
8
+ # rbnacl is not supported on windows.
9
+ windows = Gem.win_platform?
10
+
11
+ if windows
12
+ Volt.logger.warn('Currently Message Bus encryption is not supported on windows.')
13
+ end
14
+
8
15
  # Message bus is encrypted by default
9
- @encrypted = (Volt.config.message_bus.try(:disable_encryption) != true)
16
+ disable = Volt.config.message_bus.try(:disable_encryption)
17
+ @encrypted = !windows && (disable != true)
10
18
 
11
19
  if @encrypted
12
20
  # Setup a RbNaCl simple box for handling encryption
@@ -27,7 +27,14 @@ module Volt
27
27
  end
28
28
 
29
29
  def generate_config_code
30
- "\nVolt.setup_client_config(#{Volt.config.public.to_h.inspect})\n"
30
+ # Setup Volt.config on the client
31
+ code = "\nVolt.setup_client_config(#{Volt.config.public.to_h.inspect})\n"
32
+
33
+ # Include the root initializers
34
+ code << "require_tree '#{Volt.root}/config/initializers'\n"
35
+ code << "require_tree '#{Volt.root}/config/initializers/client'\n"
36
+
37
+ code
31
38
  end
32
39
  end
33
40
  end
@@ -4,12 +4,15 @@ require 'volt/router/routes'
4
4
  # Serves the main pages
5
5
  module Volt
6
6
  class IndexFiles
7
- def initialize(app, component_paths, opal_files)
7
+ def initialize(app, volt_app, component_paths, opal_files)
8
8
  @app = app
9
+ @volt_app = volt_app
9
10
  @component_paths = component_paths
10
11
  @opal_files = opal_files
11
12
 
12
- @@router ||= Routes.new.define do
13
+ @@router = volt_app.router
14
+
15
+ @@router.define do
13
16
  # Load routes for each component
14
17
  component_paths.components.values.flatten.uniq.each do |component_path|
15
18
  routes_path = "#{component_path}/config/routes.rb"
@@ -1,3 +1,5 @@
1
+ require 'volt/controllers/collection_helpers'
2
+
1
3
  module Volt
2
4
  class Task
3
5
  if RUBY_PLATFORM == 'opal'
@@ -14,8 +16,11 @@ module Volt
14
16
  $page.tasks.call(self.name, name, meta_data, *args, &block)
15
17
  end
16
18
  else
19
+ include CollectionHelpers
20
+
17
21
  def initialize(volt_app, channel = nil, dispatcher = nil)
18
- @volt_app = volt_app
22
+ @volt_app = volt_app
23
+ @page = volt_app.page
19
24
  @channel = channel
20
25
  @dispatcher = dispatcher
21
26
  end
@@ -39,11 +44,6 @@ module Volt
39
44
  new(Volt.current_app, nil, nil).send(name, *args, &block)
40
45
  end.resolve(nil)
41
46
  end
42
-
43
- # Provide access to the store collection
44
- def store
45
- $page.store
46
- end
47
47
  end
48
48
  end
49
49
  end
@@ -0,0 +1,429 @@
1
+ # A copy of the opal 0.8 promise library. The one in 0.7.x has some bugs.
2
+
3
+ # {Promise} is used to help structure asynchronous code.
4
+ #
5
+ # It is available in the Opal standard library, and can be required in any Opal
6
+ # application:
7
+ #
8
+ # require 'promise'
9
+ #
10
+ # ## Basic Usage
11
+ #
12
+ # Promises are created and returned as objects with the assumption that they
13
+ # will eventually be resolved or rejected, but never both. A {Promise} has
14
+ # a {#then} and {#fail} method (or one of their aliases) that can be used to
15
+ # register a block that gets called once resolved or rejected.
16
+ #
17
+ # promise = Promise.new
18
+ #
19
+ # promise.then {
20
+ # puts "resolved!"
21
+ # }.fail {
22
+ # puts "rejected!"
23
+ # }
24
+ #
25
+ # # some time later
26
+ # promise.resolve
27
+ #
28
+ # # => "resolved!"
29
+ #
30
+ # It is important to remember that a promise can only be resolved or rejected
31
+ # once, so the block will only ever be called once (or not at all).
32
+ #
33
+ # ## Resolving Promises
34
+ #
35
+ # To resolve a promise, means to inform the {Promise} that it has succeeded
36
+ # or evaluated to a useful value. {#resolve} can be passed a value which is
37
+ # then passed into the block handler:
38
+ #
39
+ # def get_json
40
+ # promise = Promise.new
41
+ #
42
+ # HTTP.get("some_url") do |req|
43
+ # promise.resolve req.json
44
+ # end
45
+ #
46
+ # promise
47
+ # end
48
+ #
49
+ # get_json.then do |json|
50
+ # puts "got some JSON from server"
51
+ # end
52
+ #
53
+ # ## Rejecting Promises
54
+ #
55
+ # Promises are also designed to handle error cases, or situations where an
56
+ # outcome is not as expected. Taking the previous example, we can also pass
57
+ # a value to a {#reject} call, which passes that object to the registered
58
+ # {#fail} handler:
59
+ #
60
+ # def get_json
61
+ # promise = Promise.new
62
+ #
63
+ # HTTP.get("some_url") do |req|
64
+ # if req.ok?
65
+ # promise.resolve req.json
66
+ # else
67
+ # promise.reject req
68
+ # end
69
+ #
70
+ # promise
71
+ # end
72
+ #
73
+ # get_json.then {
74
+ # # ...
75
+ # }.fail { |req|
76
+ # puts "it went wrong: #{req.message}"
77
+ # }
78
+ #
79
+ # ## Chaining Promises
80
+ #
81
+ # Promises become even more useful when chained together. Each {#then} or
82
+ # {#fail} call returns a new {Promise} which can be used to chain more and more
83
+ # handlers together.
84
+ #
85
+ # promise.then { wait_for_something }.then { do_something_else }
86
+ #
87
+ # Rejections are propagated through the entire chain, so a "catch all" handler
88
+ # can be attached at the end of the tail:
89
+ #
90
+ # promise.then { ... }.then { ... }.fail { ... }
91
+ #
92
+ # ## Composing Promises
93
+ #
94
+ # {Promise.when} can be used to wait for more than one promise to resolve (or
95
+ # reject). Using the previous example, we could request two different json
96
+ # requests and wait for both to finish:
97
+ #
98
+ # Promise.when(get_json, get_json2).then |first, second|
99
+ # puts "got two json payloads: #{first}, #{second}"
100
+ # end
101
+ #
102
+ class Promise
103
+ def self.value(value)
104
+ new.resolve(value)
105
+ end
106
+
107
+ def self.error(value)
108
+ new.reject(value)
109
+ end
110
+
111
+ def self.when(*promises)
112
+ When.new(promises)
113
+ end
114
+
115
+ attr_reader :error, :prev, :next
116
+
117
+ def initialize(action = {})
118
+ @action = action
119
+
120
+ @realized = false
121
+ @exception = false
122
+ @value = nil
123
+ @error = nil
124
+ @delayed = false
125
+
126
+ @prev = nil
127
+ @next = nil
128
+ end
129
+
130
+ def value
131
+ if Promise === @value
132
+ @value.value
133
+ else
134
+ @value
135
+ end
136
+ end
137
+
138
+ def act?
139
+ @action.has_key?(:success) || @action.has_key?(:always)
140
+ end
141
+
142
+ def action
143
+ @action.keys
144
+ end
145
+
146
+ def exception?
147
+ @exception
148
+ end
149
+
150
+ def realized?
151
+ !!@realized
152
+ end
153
+
154
+ def resolved?
155
+ @realized == :resolve
156
+ end
157
+
158
+ def rejected?
159
+ @realized == :reject
160
+ end
161
+
162
+ def ^(promise)
163
+ promise << self
164
+ self >> promise
165
+
166
+ promise
167
+ end
168
+
169
+ def <<(promise)
170
+ @prev = promise
171
+
172
+ self
173
+ end
174
+
175
+ def >>(promise)
176
+ @next = promise
177
+
178
+ if exception?
179
+ promise.reject(@delayed[0])
180
+ elsif resolved?
181
+ promise.resolve(@delayed ? @delayed[0] : value)
182
+ elsif rejected?
183
+ if !@action.has_key?(:failure) || Promise === (@delayed ? @delayed[0] : @error)
184
+ promise.reject(@delayed ? @delayed[0] : error)
185
+ elsif promise.action.include?(:always)
186
+ promise.reject(@delayed ? @delayed[0] : error)
187
+ end
188
+ end
189
+
190
+ self
191
+ end
192
+
193
+ def resolve(value = nil)
194
+ if realized?
195
+ raise ArgumentError, 'the promise has already been realized'
196
+ end
197
+
198
+ if Promise === value
199
+ return (value << @prev) ^ self
200
+ end
201
+
202
+ begin
203
+ if block = @action[:success] || @action[:always]
204
+ value = block.call(value)
205
+ end
206
+
207
+ resolve!(value)
208
+ rescue Exception => e
209
+ exception!(e)
210
+ end
211
+
212
+ self
213
+ end
214
+
215
+ def resolve!(value)
216
+ @realized = :resolve
217
+ @value = value
218
+
219
+ if @next
220
+ @next.resolve(value)
221
+ else
222
+ @delayed = [value]
223
+ end
224
+ end
225
+
226
+ def reject(value = nil)
227
+ if realized?
228
+ raise ArgumentError, 'the promise has already been realized'
229
+ end
230
+
231
+ if Promise === value
232
+ return (value << @prev) ^ self
233
+ end
234
+
235
+ begin
236
+ if block = @action[:failure] || @action[:always]
237
+ value = block.call(value)
238
+ end
239
+
240
+ if @action.has_key?(:always)
241
+ resolve!(value)
242
+ else
243
+ reject!(value)
244
+ end
245
+ rescue Exception => e
246
+ exception!(e)
247
+ end
248
+
249
+ self
250
+ end
251
+
252
+ def reject!(value)
253
+ @realized = :reject
254
+ @error = value
255
+
256
+ if @next
257
+ @next.reject(value)
258
+ else
259
+ @delayed = [value]
260
+ end
261
+ end
262
+
263
+ def exception!(error)
264
+ @exception = true
265
+
266
+ reject!(error)
267
+ end
268
+
269
+ def then(&block)
270
+ if @next
271
+ raise ArgumentError, 'a promise has already been chained'
272
+ end
273
+
274
+ self ^ Promise.new(success: block)
275
+ end
276
+
277
+ alias do then
278
+
279
+ def fail(&block)
280
+ if @next
281
+ raise ArgumentError, 'a promise has already been chained'
282
+ end
283
+
284
+ self ^ Promise.new(failure: block)
285
+ end
286
+
287
+ alias rescue fail
288
+ alias catch fail
289
+
290
+ def always(&block)
291
+ if @next
292
+ raise ArgumentError, 'a promise has already been chained'
293
+ end
294
+
295
+ self ^ Promise.new(always: block)
296
+ end
297
+
298
+ alias finally always
299
+ alias ensure always
300
+
301
+ def trace(depth = nil, &block)
302
+ if @next
303
+ raise ArgumentError, 'a promise has already been chained'
304
+ end
305
+
306
+ self ^ Trace.new(depth, block)
307
+ end
308
+
309
+ def inspect
310
+ result = "#<#{self.class}(#{object_id})"
311
+
312
+ if @next
313
+ result += " >> #{@next.inspect}"
314
+ end
315
+
316
+ if realized?
317
+ result += ": #{(@value || @error).inspect}>"
318
+ else
319
+ result += ">"
320
+ end
321
+
322
+ result
323
+ end
324
+
325
+ class Trace < self
326
+ def self.it(promise)
327
+ current = []
328
+
329
+ if promise.act? || promise.prev.nil?
330
+ current.push(promise.value)
331
+ end
332
+
333
+ if prev = promise.prev
334
+ current.concat(it(prev))
335
+ else
336
+ current
337
+ end
338
+ end
339
+
340
+ def initialize(depth, block)
341
+ @depth = depth
342
+
343
+ super success: -> {
344
+ trace = Trace.it(self).reverse
345
+ trace.pop
346
+
347
+ if depth && depth <= trace.length
348
+ trace.shift(trace.length - depth)
349
+ end
350
+
351
+ block.call(*trace)
352
+ }
353
+ end
354
+ end
355
+
356
+ class When < self
357
+ def initialize(promises = [])
358
+ super()
359
+
360
+ @wait = []
361
+
362
+ promises.each {|promise|
363
+ wait promise
364
+ }
365
+ end
366
+
367
+ def each(&block)
368
+ raise ArgumentError, 'no block given' unless block
369
+
370
+ self.then {|values|
371
+ values.each(&block)
372
+ }
373
+ end
374
+
375
+ def collect(&block)
376
+ raise ArgumentError, 'no block given' unless block
377
+
378
+ self.then {|values|
379
+ When.new(values.map(&block))
380
+ }
381
+ end
382
+
383
+ def inject(*args, &block)
384
+ self.then {|values|
385
+ values.reduce(*args, &block)
386
+ }
387
+ end
388
+
389
+ alias map collect
390
+
391
+ alias reduce inject
392
+
393
+ def wait(promise)
394
+ unless Promise === promise
395
+ promise = Promise.value(promise)
396
+ end
397
+
398
+ if promise.act?
399
+ promise = promise.then
400
+ end
401
+
402
+ @wait << promise
403
+
404
+ promise.always {
405
+ try if @next
406
+ }
407
+
408
+ self
409
+ end
410
+
411
+ alias and wait
412
+
413
+ def >>(*)
414
+ super.tap {
415
+ try
416
+ }
417
+ end
418
+
419
+ def try
420
+ if @wait.all?(&:realized?)
421
+ if promise = @wait.find(&:rejected?)
422
+ reject(promise.error)
423
+ else
424
+ resolve(@wait.map(&:value))
425
+ end
426
+ end
427
+ end
428
+ end
429
+ end