volt 0.9.6 → 0.9.7.pre2

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