volt 0.9.6 → 0.9.7.pre2

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 (167) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/CHANGELOG.md +26 -0
  4. data/Gemfile +6 -1
  5. data/README.md +2 -0
  6. data/Rakefile +1 -0
  7. data/app/volt/models/active_volt_instance.rb +8 -6
  8. data/app/volt/models/user.rb +11 -2
  9. data/app/volt/models/volt_app_property.rb +8 -0
  10. data/app/volt/tasks/query_tasks.rb +23 -31
  11. data/app/volt/tasks/store_tasks.rb +2 -2
  12. data/app/volt/tasks/volt_admin_tasks.rb +24 -0
  13. data/docs/UPGRADE_GUIDE.md +6 -0
  14. data/lib/volt.rb +19 -12
  15. data/lib/volt/boot.rb +1 -0
  16. data/lib/volt/cli.rb +19 -8
  17. data/lib/volt/cli/console.rb +0 -1
  18. data/lib/volt/cli/generators.rb +14 -3
  19. data/lib/volt/cli/migrate.rb +26 -0
  20. data/lib/volt/config.rb +17 -4
  21. data/lib/volt/controllers/http_controller.rb +12 -0
  22. data/lib/volt/data_stores/base_adaptor_client.rb +2 -2
  23. data/lib/volt/data_stores/base_adaptor_server.rb +2 -0
  24. data/lib/volt/data_stores/data_store.rb +20 -14
  25. data/lib/volt/extra_core/class.rb +28 -14
  26. data/lib/volt/extra_core/hash.rb +5 -0
  27. data/lib/volt/extra_core/string.rb +3 -1
  28. data/lib/volt/helpers/time.rb +9 -43
  29. data/lib/volt/helpers/time/calculations.rb +204 -0
  30. data/lib/volt/helpers/time/distance.rb +63 -0
  31. data/lib/volt/helpers/time/duration.rb +71 -0
  32. data/lib/volt/helpers/time/local_calculations.rb +49 -0
  33. data/lib/volt/helpers/time/local_volt_time.rb +23 -0
  34. data/lib/volt/helpers/time/numeric.rb +59 -0
  35. data/lib/volt/helpers/time/volt_time.rb +170 -0
  36. data/lib/volt/models.rb +5 -0
  37. data/lib/volt/models/array_model.rb +33 -6
  38. data/lib/volt/models/associations.rb +146 -23
  39. data/lib/volt/models/buffer.rb +38 -41
  40. data/lib/volt/models/cursor.rb +15 -0
  41. data/lib/volt/models/errors.rb +11 -0
  42. data/lib/volt/models/field_helpers.rb +108 -68
  43. data/lib/volt/models/helpers/array_model.rb +4 -0
  44. data/lib/volt/models/helpers/base.rb +8 -1
  45. data/lib/volt/models/helpers/change_helpers.rb +31 -12
  46. data/lib/volt/models/helpers/defaults.rb +15 -0
  47. data/lib/volt/models/location.rb +20 -6
  48. data/lib/volt/models/migrations/migration.rb +23 -0
  49. data/lib/volt/models/migrations/migration_runner.rb +146 -0
  50. data/lib/volt/models/model.rb +38 -1
  51. data/lib/volt/models/permissions.rb +8 -1
  52. data/lib/volt/models/persistors/array_store.rb +87 -8
  53. data/lib/volt/models/persistors/base.rb +19 -0
  54. data/lib/volt/models/persistors/model_store.rb +1 -1
  55. data/lib/volt/models/persistors/page.rb +4 -1
  56. data/lib/volt/models/persistors/query/query_identifier.rb +102 -0
  57. data/lib/volt/models/persistors/query/query_listener.rb +57 -12
  58. data/lib/volt/models/root_models/root_models.rb +19 -0
  59. data/lib/volt/models/url.rb +11 -2
  60. data/lib/volt/models/validations/validations.rb +5 -2
  61. data/lib/volt/models/validators/type_validator.rb +11 -0
  62. data/lib/volt/models/validators/unique_validator.rb +2 -2
  63. data/lib/volt/page/bindings/attribute_binding.rb +23 -1
  64. data/lib/volt/page/targets/attribute_section.rb +7 -0
  65. data/lib/volt/page/targets/binding_document/component_node.rb +44 -18
  66. data/lib/volt/page/targets/binding_document/tag_node.rb +41 -0
  67. data/lib/volt/page/tasks.rb +16 -8
  68. data/lib/volt/queries/live_query.rb +109 -0
  69. data/lib/volt/queries/live_query_pool.rb +58 -0
  70. data/lib/volt/queries/live_subquery.rb +0 -0
  71. data/lib/volt/queries/query_association_splitter.rb +31 -0
  72. data/lib/volt/queries/query_diff.rb +100 -0
  73. data/lib/volt/queries/query_runner.rb +110 -0
  74. data/lib/volt/queries/query_subscription.rb +80 -0
  75. data/lib/volt/queries/query_subscription_pool.rb +37 -0
  76. data/lib/volt/reactive/eventable.rb +8 -0
  77. data/lib/volt/reactive/reactive_array.rb +0 -4
  78. data/lib/volt/router/routes.rb +81 -31
  79. data/lib/volt/server/message_bus/base_message_bus.rb +9 -3
  80. data/lib/volt/server/message_bus/peer_to_peer.rb +6 -6
  81. data/lib/volt/server/message_bus/peer_to_peer/server_tracker.rb +1 -1
  82. data/lib/volt/server/middleware/default_middleware_stack.rb +12 -8
  83. data/lib/volt/server/rack/component_paths.rb +31 -4
  84. data/lib/volt/server/rack/http_content_types.rb +62 -0
  85. data/lib/volt/server/rack/http_resource.rb +1 -1
  86. data/lib/volt/server/rack/index_files.rb +8 -1
  87. data/lib/volt/server/rack/opal_files.rb +16 -1
  88. data/lib/volt/server/rack/sprockets_helpers_setup.rb +32 -1
  89. data/lib/volt/server/socket_connection_handler.rb +16 -7
  90. data/lib/volt/server/template_handlers/sprockets_component_handler.rb +5 -3
  91. data/lib/volt/spec/capybara.rb +4 -3
  92. data/lib/volt/spec/setup.rb +5 -0
  93. data/lib/volt/tasks/dispatcher.rb +3 -1
  94. data/lib/volt/utils/data_transformer.rb +4 -4
  95. data/lib/volt/utils/ejson.rb +19 -6
  96. data/lib/volt/utils/promise_extensions.rb +1 -1
  97. data/lib/volt/utils/time_opal_patch.rb +749 -0
  98. data/lib/volt/utils/time_patch.rb +11 -4
  99. data/lib/volt/version.rb +1 -1
  100. data/lib/volt/volt/app.rb +19 -11
  101. data/lib/volt/volt/properties.rb +24 -0
  102. data/lib/volt/volt/server_setup/app.rb +30 -7
  103. data/lib/volt/volt/users.rb +15 -3
  104. data/spec/apps/kitchen_sink/Gemfile +5 -1
  105. data/spec/apps/kitchen_sink/app/main/config/routes.rb +1 -0
  106. data/spec/apps/kitchen_sink/app/main/controllers/save_controller.rb +1 -1
  107. data/spec/apps/kitchen_sink/app/main/controllers/server/simple_http_controller.rb +4 -0
  108. data/spec/apps/kitchen_sink/app/main/controllers/todos_controller.rb +4 -2
  109. data/spec/apps/kitchen_sink/app/main/models/post.rb +0 -1
  110. data/spec/apps/kitchen_sink/app/main/models/todo.rb +4 -0
  111. data/spec/apps/kitchen_sink/app/main/views/mailers/reset_password.html +10 -0
  112. data/spec/apps/kitchen_sink/app/main/views/todos/index.html +2 -0
  113. data/spec/apps/kitchen_sink/config/app.rb +2 -0
  114. data/spec/apps/migrations/config/db/migrations/1445111704_migration1.rb +7 -0
  115. data/spec/apps/migrations/config/db/migrations/1445113517_migration2.rb +7 -0
  116. data/spec/apps/migrations/config/db/migrations/1445115200_migration3.rb +7 -0
  117. data/spec/extra_core/class_spec.rb +10 -0
  118. data/spec/helpers/distance_spec.rb +35 -0
  119. data/spec/helpers/duration_spec.rb +160 -0
  120. data/spec/helpers/volt_time_spec.rb +275 -0
  121. data/spec/integration/callbacks_spec.rb +2 -1
  122. data/spec/integration/http_endpoints_spec.rb +4 -0
  123. data/spec/integration/save_spec.rb +1 -1
  124. data/spec/integration/todos_spec.rb +7 -5
  125. data/spec/models/array_model_spec.rb +17 -3
  126. data/spec/models/associations_spec.rb +48 -1
  127. data/spec/models/field_helpers_spec.rb +7 -3
  128. data/spec/models/migrations/migration_runner_spec.rb +69 -0
  129. data/spec/models/model_spec.rb +42 -8
  130. data/spec/models/permissions_spec.rb +20 -8
  131. data/spec/models/persistors/array_store_spec.rb +18 -0
  132. data/spec/models/persistors/page_spec.rb +15 -10
  133. data/spec/models/persistors/store_spec.rb +13 -3
  134. data/spec/models/url_spec.rb +4 -3
  135. data/spec/models/user_spec.rb +6 -3
  136. data/spec/models/user_validation_spec.rb +3 -3
  137. data/spec/models/validations_spec.rb +4 -0
  138. data/spec/models/validators/block_validations_spec.rb +9 -5
  139. data/spec/models/validators/email_validator_spec.rb +2 -0
  140. data/spec/models/validators/lifecycle_callbacks_spec.rb +86 -0
  141. data/spec/models/validators/unique_validator_spec.rb +1 -0
  142. data/spec/page/path_string_renderer_spec.rb +5 -0
  143. data/spec/queries/live_query_spec.rb +16 -0
  144. data/spec/queries/query_association_splitter_spec.rb +14 -0
  145. data/spec/queries/query_diff_spec.rb +132 -0
  146. data/spec/queries/query_identifier_spec.rb +98 -0
  147. data/spec/queries/query_runner_spec.rb +63 -0
  148. data/spec/queries/query_tracker_spec.rb +141 -0
  149. data/spec/router/routes_spec.rb +52 -21
  150. data/spec/server/middleware/rack_content_types_spec.rb +78 -0
  151. data/spec/server/rack/asset_files_spec.rb +38 -30
  152. data/spec/spec_helper.rb +8 -0
  153. data/spec/utils/ejson_spec.rb +9 -8
  154. data/spec/utils/ejson_volt_time_spec.rb +65 -0
  155. data/templates/migration/migration.rb.tt +9 -0
  156. data/templates/newgem/gitignore.tt +1 -0
  157. data/templates/project/Gemfile.tt +19 -2
  158. data/templates/project/README.md.tt +6 -1
  159. data/templates/project/app/main/config/dependencies.rb +6 -0
  160. data/templates/project/config/app.rb.tt +18 -4
  161. data/volt.gemspec +2 -2
  162. metadata +73 -16
  163. data/app/volt/tasks/live_query/live_query_pool.rb +0 -48
  164. data/app/volt/tasks/live_query/query_tracker.rb +0 -92
  165. data/spec/tasks/live_query_spec.rb +0 -18
  166. data/spec/tasks/query_tasks.rb +0 -7
  167. data/spec/tasks/query_tracker_spec.rb +0 -145
@@ -5,38 +5,38 @@ module Volt
5
5
  # a url to params, and params to url.
6
6
  # routes do
7
7
  # client "/about", _view: 'about'
8
- # client "/blog/{id}/edit", _view: 'blog/edit', _action: 'edit'
9
- # client "/blog/{id}", _view: 'blog/show', _action: 'show'
10
- # client "/blog", _view: 'blog'
11
- # client "/blog/new", _view: 'blog/new', _action: 'new'
12
- # client "/cool/{_name}", _view: 'cool'
8
+ # client "/blog/{{ id }}/edit", view: 'blog/edit', action: 'edit'
9
+ # client "/blog/{{ id }}", view: 'blog/show', action: 'show'
10
+ # client "/blog", view: 'blog'
11
+ # client "/blog/new", view: 'blog/new', action: 'new'
12
+ # client "/cool/{{ name }}", view: 'cool'
13
13
  # end
14
14
  #
15
15
  # Using the routes above, we would generate the following:
16
16
  #
17
17
  # @direct_routes = {
18
- # '/about' => {_view: 'about'},
19
- # '/blog' => {_view: 'blog'}
20
- # '/blog/new' => {_view: 'blog/new', _action: 'new'}
18
+ # '/about' => {view: 'about'},
19
+ # '/blog' => {view: 'blog'}
20
+ # '/blog/new' => {view: 'blog/new', action: 'new'}
21
21
  # }
22
22
  #
23
23
  # -- nil represents a terminal
24
- # -- * represents any match
24
+ # -- * represents any match (in that section (between / and /))
25
25
  # -- a number for a parameter means use the value in that number section
26
26
  #
27
27
  # @indirect_routes = {
28
28
  # '*' => {
29
29
  # 'edit' => {
30
- # nil => {id: 1, _view: 'blog/edit', _action: 'edit'}
30
+ # nil => {id: 1, view: 'blog/edit', action: 'edit'}
31
31
  # }
32
- # nil => {id: 1, _view: 'blog/show', _action: 'show'}
32
+ # nil => {id: 1, view: 'blog/show', action: 'show'}
33
33
  # }
34
34
  # }
35
35
  # }
36
36
  #
37
37
  # Match for params
38
38
  # @param_matches = [
39
- # {id: nil, _view: 'blog/edit', _action: 'edit'} => Proc.new {|params| "/blog/#{params.id}/edit", params.reject {|k,v| k == :id }}
39
+ # {id: nil, view: 'blog/edit', action: 'edit'} => Proc.new {|params| "/blog/#{params.id}/edit", params.reject {|k,v| k == :id }}
40
40
  # ]
41
41
  class Routes
42
42
  def initialize
@@ -68,7 +68,7 @@ module Volt
68
68
  end
69
69
 
70
70
  # Add server side routes
71
-
71
+
72
72
  def get(path, params)
73
73
  create_route(:get, path, params)
74
74
  end
@@ -190,17 +190,29 @@ module Volt
190
190
  if part.nil?
191
191
  if node[part]
192
192
  # We found a match, replace the bindings and return
193
- # TODO: Handvle nested
193
+ # TODO: Handle nested
194
194
  setup_bindings_in_params(original_parts, node[part])
195
195
  else
196
196
  false
197
197
  end
198
- elsif (new_node = node[part])
199
- # Direct match for section, continue
200
- match_path(original_parts, parts, new_node)
201
- elsif (new_node = node['*'])
202
- # Match on binding section
203
- match_path(original_parts, parts, new_node)
198
+ else
199
+ if (new_node = node[part])
200
+ # Direct match for section, continue
201
+ result = match_path(original_parts, parts, new_node)
202
+ return result if result
203
+ end
204
+ if (new_node = node['*'])
205
+ # Match on binding single section
206
+ result = match_path(original_parts, parts, new_node)
207
+ return result if result
208
+ end
209
+ if ((params = node['**']) && params && (params = params[nil]))
210
+ # Match on binding multiple sections
211
+ result = setup_bindings_in_params(original_parts, params)
212
+ return result if result
213
+ end
214
+
215
+ return false
204
216
  end
205
217
  end
206
218
 
@@ -214,6 +226,10 @@ module Volt
214
226
  if value.is_a?(Fixnum)
215
227
  # Lookup the param's value in the original url parts
216
228
  params[key] = original_parts[value]
229
+ elsif value.is_a?(Range)
230
+ # When doing multiple section bindings, we lookup the parts as a range
231
+ # then join them with /
232
+ params[key] = original_parts[value].join('/')
217
233
  end
218
234
  end
219
235
 
@@ -231,10 +247,24 @@ module Volt
231
247
  parts.each_with_index do |part, index|
232
248
  if has_binding?(part)
233
249
  # Strip off {{ and }}
234
- params[part[2...-2].strip.to_sym] = index
250
+ section_matcher, multipart = binding_extract(part)
251
+
252
+ if multipart
253
+ # Match anything for the rest of the url (multiple sections)
254
+
255
+ # check that the splat is at the end of the url
256
+ if index != (parts.size-1)
257
+ raise "The splat (*) operator can only be used at the end of a url"
258
+ end
259
+
260
+ part = '**'
261
+ index = (index..-1)
262
+ else
263
+ # Match anything in a single section, set the part to be '*'
264
+ part = '*'
265
+ end
235
266
 
236
- # Set the part to be '*' (anything matcher)
237
- part = '*'
267
+ params[section_matcher] = index
238
268
  end
239
269
 
240
270
  node = (node[part] ||= {})
@@ -251,7 +281,8 @@ module Volt
251
281
  if has_binding?(part)
252
282
  # Setup a nil param that can match anything, but gets
253
283
  # assigned into the url
254
- params[part[2...-2].strip.to_sym] = nil
284
+ section_matcher, _ = binding_extract(part)
285
+ params[section_matcher] = nil
255
286
  end
256
287
  end
257
288
 
@@ -269,12 +300,12 @@ module Volt
269
300
 
270
301
  url = parts.map do |part|
271
302
  val = if has_binding?(part)
272
- # Get the
273
- binding = part[2...-2].strip.to_sym
274
- input_params.delete(binding)
275
- else
276
- part
277
- end
303
+ # Get the
304
+ binding, _ = binding_extract(part)
305
+ input_params.delete(binding)
306
+ else
307
+ part
308
+ end
278
309
 
279
310
  val
280
311
  end.join('/')
@@ -319,7 +350,9 @@ module Volt
319
350
  end
320
351
 
321
352
  def url_parts(path)
322
- path.split('/').reject(&:blank?)
353
+ # Remove start and end / and then split, keeping any blanks between
354
+ # sections (so // would get rejoined with the double)
355
+ path.gsub(/^\//, '').chomp('/').split('/', -1)
323
356
  end
324
357
 
325
358
  # Check if a string has a binding in it
@@ -331,5 +364,22 @@ module Volt
331
364
  def path_with_id(base_path)
332
365
  base_path + '/{{ id }}'
333
366
  end
367
+
368
+ # @param: The binding part of the url eg: "{{ binding }} or {{ *binding }}"
369
+ # @return: a symbol for the binding variable eg: :binding, and a boolean for
370
+ # if the binding matches multiple sections.
371
+ def binding_extract(part)
372
+ section_matcher = part[2...-2].strip
373
+
374
+ multipart_matcher = false
375
+
376
+ if section_matcher[0] == '*'
377
+ # Match anything for the rest of the url (multiple sections)
378
+ section_matcher = section_matcher[1..-1]
379
+ multipart_matcher = true
380
+ end
381
+
382
+ return section_matcher.to_sym, multipart_matcher
383
+ end
334
384
  end
335
385
  end
@@ -5,13 +5,15 @@
5
5
  #
6
6
  # MessageBus instances inherit from MessageBus::BaseMessageBus and provide
7
7
  # two methods 'publish' and 'subscribe'. They should be inside of
8
- # Volt::MessageBus.
8
+ # Volt::MessageBus. Be sure to
9
+ # ```require 'volt/server/message_bus/base_message_bus'```
9
10
  #
10
11
  # publish should take a channel name and a message and deliver the message to
11
12
  # any subscried listeners.
12
13
  #
13
14
  # subscribe should take a channel name and a block. It should yield a message
14
- # to the block if a message is published to the channel.
15
+ # to the block if a message is published to the channel. It should return an
16
+ # object with a ```remove``` method that will remove the subscription.
15
17
  #
16
18
  # The implementation details of the pub/sub connection are left to the
17
19
  # implemntation. If the user needs to configure server addresses, Volt.config
@@ -29,9 +31,13 @@
29
31
  # NOTE: in the future, we plan to add support for round robbin message receiving
30
32
  # and other patterns.
31
33
 
34
+ require 'volt/reactive/eventable'
35
+
32
36
  module Volt
33
37
  module MessageBus
34
38
  class BaseMessageBus
39
+ include Eventable
40
+
35
41
  # MessagesBus's should take an instance of a Volt::App
36
42
  def initialize(volt_app)
37
43
  raise "Not implemented"
@@ -54,4 +60,4 @@ module Volt
54
60
  end
55
61
  end
56
62
  end
57
- end
63
+ end
@@ -40,6 +40,7 @@ require 'volt/server/message_bus/peer_to_peer/server_tracker'
40
40
  require 'volt/server/message_bus/peer_to_peer/peer_server'
41
41
  require 'volt/server/message_bus/peer_to_peer/peer_connection'
42
42
  require 'volt/server/message_bus/base_message_bus'
43
+ require_relative '../../../../app/volt/models/active_volt_instance'
43
44
 
44
45
  # TODO: Right now the message bus uses threads, we should switch it to use a
45
46
  # single thread and some form of select:
@@ -50,7 +51,6 @@ module Volt
50
51
  class PeerToPeer < BaseMessageBus
51
52
  # How long without an update before we mark an instance as dead (in seconds)
52
53
  DEAD_TIME = 20
53
- include Eventable
54
54
 
55
55
  # Use subscribe instead of on provided in Eventable
56
56
  alias_method :subscribe, :on
@@ -60,7 +60,7 @@ module Volt
60
60
  def initialize(volt_app)
61
61
  @volt_app = volt_app
62
62
 
63
- if Volt::DataStore.fetch.connected?
63
+ if Volt::DataStore.fetch(volt_app).connected?
64
64
  # Generate a guid
65
65
  @server_id = SecureRandom.uuid
66
66
  # The PeerConnection's to peers
@@ -112,9 +112,9 @@ module Volt
112
112
 
113
113
  # Return an array of peer records.
114
114
  def peers
115
- instances = @volt_app.store._active_volt_instances
115
+ instances = @volt_app.store.active_volt_instances
116
116
 
117
- instances.where(server_id: {'$ne' => @server_id}).all.sync
117
+ instances.where {|svr| svr.server_id !~ @server_id }.all.sync
118
118
  end
119
119
 
120
120
  def connect_to_peers
@@ -186,7 +186,7 @@ module Volt
186
186
  if peer
187
187
  # Found the peer, retry if it has reported in in the last 2
188
188
  # minutes.
189
- if peer._time > (Time.now.to_i - DEAD_TIME)
189
+ if peer._time.to_i > (Time.now.to_i - DEAD_TIME)
190
190
  # Peer reported in less than 2 minutes ago
191
191
  return true
192
192
  else
@@ -200,4 +200,4 @@ module Volt
200
200
 
201
201
  end
202
202
  end
203
- end
203
+ end
@@ -36,7 +36,7 @@ module Volt
36
36
  instances = @volt_app.store.active_volt_instances
37
37
  instances.where(server_id: @server_id).first.then do |item|
38
38
  ips = local_ips.join(',')
39
- time = Time.now.to_i
39
+ time = Time.now
40
40
  if item
41
41
  item.assign_attributes(ips: ips, time: time, port: @port)
42
42
  else
@@ -7,6 +7,7 @@ require 'volt/server/rack/opal_files'
7
7
  require 'volt/server/rack/index_files'
8
8
  require 'volt/server/rack/http_resource'
9
9
  require 'volt/server/rack/sprockets_helpers_setup'
10
+ require 'volt/server/rack/http_content_types'
10
11
 
11
12
 
12
13
 
@@ -14,16 +15,17 @@ module Volt
14
15
  class DefaultMiddlewareStack
15
16
  # Setup on the middleware we can setup before booting components
16
17
  def self.preboot_setup(volt_app, rack_app)
18
+ rack_app.use Rack::Chunked
17
19
  # Should only be used in production
18
20
  if Volt.config.deflate
19
21
  rack_app.use Rack::Deflater
20
- rack_app.use Rack::Chunked
21
22
  end
22
23
 
23
24
  rack_app.use Rack::ContentLength
24
25
  rack_app.use Rack::KeepAlive
25
26
  rack_app.use Rack::ConditionalGet
26
27
  rack_app.use Rack::ETag
28
+ rack_app.use Rack::HttpContentTypes
27
29
 
28
30
  rack_app.use Rack::Session::Cookie, {
29
31
  key: 'rack.session',
@@ -42,6 +44,7 @@ module Volt
42
44
  # Setup the middleware that we need to wait for components to boot before we
43
45
  # can set them up.
44
46
  def self.postboot_setup(volt_app, rack_app)
47
+
45
48
  # Serve the opal files
46
49
  opal_files = OpalFiles.new(rack_app, volt_app.app_url, volt_app.app_path, volt_app.component_paths)
47
50
  volt_app.opal_files = opal_files
@@ -55,16 +58,17 @@ module Volt
55
58
 
56
59
  rack_app.use HttpResource, volt_app, volt_app.router
57
60
 
61
+
58
62
  # serve assets from public
59
63
  rack_app.use Rack::Static,
60
- urls: [''],
61
- root: 'public',
62
- index: 'index.html',
63
- header_rules: [
64
- [:all, { 'Cache-Control' => 'public, max-age=86400' }]
65
- ]
64
+ urls: [''],
65
+ root: ['public'],
66
+ index: 'index.html',
67
+ header_rules: [
68
+ [:all, { 'Cache-Control' => 'public, max-age=86400' }]
69
+ ]
66
70
 
67
71
  rack_app.run lambda { |env| [404, { 'Content-Type' => 'text/html; charset=utf-8' }, ['404 - page not found']] }
68
72
  end
69
73
  end
70
- end
74
+ end
@@ -12,13 +12,16 @@ module Volt
12
12
  # Find all app folders
13
13
  @app_folders ||= begin
14
14
  volt_app = File.expand_path(File.join(File.dirname(__FILE__), '../../../../app'))
15
- app_folders = [volt_app, "#{@root}/app", "#{@root}/vendor/app"].map { |f| File.expand_path(f) }
16
15
 
17
16
  # Gem folders with volt in them
18
17
  # TODO: we should probably qualify this a bit more
19
- app_folders += Gem.loaded_specs.values
20
- .select {|gem| gem.name =~ /^volt/ }
21
- .map {|gem| "#{gem.full_gem_path}/app" }
18
+ app_folders = [volt_app]
19
+ app_folders += Gem.loaded_specs.values.
20
+ select {|gem| gem.name =~ /^volt/ }.
21
+ sort { |a, b| dependent_sort(a, b) }.
22
+ map {|gem| "#{gem.full_gem_path}/app" }
23
+
24
+ app_folders += ["#{@root}/app", "#{@root}/vendor/app"].map { |f| File.expand_path(f) }
22
25
 
23
26
  app_folders.uniq
24
27
  end
@@ -77,6 +80,7 @@ module Volt
77
80
 
78
81
  # Delay the loading of views
79
82
  volt_app.templates.template_loader = -> { load_views_and_routes(volt_app) }
83
+ volt_app.url.routes_loader = -> { load_views_and_routes(volt_app) }
80
84
  end
81
85
  end
82
86
 
@@ -95,6 +99,10 @@ module Volt
95
99
  # Evaluate returned code, the ```volt_app``` variable is set for access.
96
100
  eval(code)
97
101
  end
102
+
103
+ # Clear template loader
104
+ volt_app.templates.template_loader = nil
105
+ volt_app.url.routes_loader = nil
98
106
  end
99
107
 
100
108
  # Returns all paths for a specific component
@@ -119,5 +127,24 @@ module Volt
119
127
 
120
128
  folders.flatten
121
129
  end
130
+
131
+ private
132
+
133
+ # Determine if Gem::Specification b dependes on a
134
+ def dependent?(a, b)
135
+ name = a.name
136
+ b.dependencies.any? {|dep| dep.type == :runtime && dep.name == a.name }
137
+ end
138
+
139
+ def dependent_sort(a, b)
140
+ if dependent?(a, b)
141
+ -1
142
+ elsif dependent?(b, a)
143
+ 1
144
+ else
145
+ 0
146
+ end
147
+ end
148
+
122
149
  end
123
150
  end
@@ -0,0 +1,62 @@
1
+ # Middleware for parsing params of different restful content in HTTP endpoints.
2
+ # Content-type: application/json - is supported out of the box
3
+ #
4
+ # Much thanks to @achiu (https://github.com/achiu) for original version of this middleware
5
+ #
6
+ # New Parsers can be added in your app's config:
7
+ # Volt.setup do |config|
8
+ # config.http_content_types = {
9
+ # parsers: {
10
+ # 'application/roll' => proc { |body| {'rick_says' => 'never gonna give you up'}}
11
+ # }
12
+ # }
13
+ # end
14
+
15
+ module Rack
16
+ class HttpContentTypes
17
+
18
+ POST_BODY = 'rack.input'.freeze
19
+ FORM_INPUT = 'rack.request.form_input'.freeze
20
+ FORM_HASH = 'rack.request.form_hash'.freeze
21
+
22
+ JSON_PARSER = proc { |data| JSON.parse data }
23
+ ERROR_HANDLER = proc { |err, type| [400, {}, ['']] }
24
+
25
+ attr_reader :parsers, :handlers, :logger
26
+
27
+ def initialize(app, options = {})
28
+ @app = app
29
+ @options = Volt.config.http_content_types ? Volt.config.http_content_types.dup : {}
30
+ @options.merge!(options)
31
+ @parsers = @options[:parsers] || {}
32
+ @handlers = @options[:handlers] || {}
33
+ unless parsers.detect { |content_type, _| 'json'.match(content_type) }
34
+ @parsers.merge!({ %r{json} => JSON_PARSER })
35
+ end
36
+ end
37
+
38
+ def call(env)
39
+ type = Rack::Request.new(env).media_type
40
+ parser = parsers.detect { |content_type, _| type.match(content_type) } if type
41
+ return @app.call(env) unless parser
42
+ body = env[POST_BODY].read ; env[POST_BODY].rewind
43
+ return @app.call(env) unless body && !body.empty?
44
+ begin
45
+ parsed = parser.last.call body
46
+ env.update FORM_HASH => parsed, FORM_INPUT => env[POST_BODY]
47
+ rescue StandardError => e
48
+ warn! e, type
49
+ handler = handlers.detect { |content_type, _| type.match(content_type) }
50
+ handler ||= ['default', ERROR_HANDLER]
51
+ return handler.last.call(e, type)
52
+ end
53
+ @app.call env
54
+ end
55
+
56
+ def warn!(error, content_type)
57
+ return unless Volt.logger
58
+ message = "[Rack::HttpContentType] Error on %s : %s" % [content_type, error.to_s]
59
+ Volt.logger.warn message
60
+ end
61
+ end
62
+ end