volt 0.9.3.pre3 → 0.9.3.pre4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -2
  3. data/app/volt/models/user.rb +5 -0
  4. data/app/volt/tasks/store_tasks.rb +2 -2
  5. data/app/volt/tasks/user_tasks.rb +1 -1
  6. data/lib/volt/cli/asset_compile.rb +0 -2
  7. data/lib/volt/cli/generate.rb +8 -3
  8. data/lib/volt/cli.rb +3 -9
  9. data/lib/volt/controllers/actions.rb +6 -1
  10. data/lib/volt/controllers/http_controller.rb +16 -7
  11. data/lib/volt/controllers/model_controller.rb +21 -0
  12. data/lib/volt/models/array_model.rb +26 -3
  13. data/lib/volt/models/model.rb +18 -19
  14. data/lib/volt/models/persistors/array_store.rb +2 -10
  15. data/lib/volt/models/root_models/store_root.rb +17 -4
  16. data/lib/volt/models/validations/validations.rb +1 -1
  17. data/lib/volt/models/validators/unique_validator.rb +1 -1
  18. data/lib/volt/models.rb +1 -1
  19. data/lib/volt/page/bindings/attribute_binding.rb +5 -4
  20. data/lib/volt/page/bindings/base_binding.rb +17 -0
  21. data/lib/volt/page/bindings/content_binding.rb +7 -5
  22. data/lib/volt/page/bindings/each_binding.rb +62 -51
  23. data/lib/volt/page/bindings/event_binding.rb +14 -0
  24. data/lib/volt/page/bindings/view_binding.rb +1 -1
  25. data/lib/volt/reactive/computation.rb +22 -13
  26. data/lib/volt/reactive/dependency.rb +0 -24
  27. data/lib/volt/router/routes.rb +35 -0
  28. data/lib/volt/server/forking_server.rb +26 -3
  29. data/lib/volt/server/message_bus/peer_to_peer/peer_connection.rb +1 -1
  30. data/lib/volt/server/message_bus/peer_to_peer/server_tracker.rb +1 -1
  31. data/lib/volt/server/message_bus/peer_to_peer.rb +28 -21
  32. data/lib/volt/server/middleware/default_middleware_stack.rb +67 -0
  33. data/lib/volt/server/middleware/middleware_stack.rb +58 -0
  34. data/lib/volt/server/rack/http_request.rb +1 -1
  35. data/lib/volt/server/rack/http_resource.rb +7 -0
  36. data/lib/volt/server/rack/keep_alive.rb +20 -0
  37. data/lib/volt/server/socket_connection_handler.rb +10 -1
  38. data/lib/volt/server.rb +6 -76
  39. data/lib/volt/utils/promise_extensions.rb +5 -1
  40. data/lib/volt/utils/set_patch.rb +25 -0
  41. data/lib/volt/utils/timers.rb +12 -0
  42. data/lib/volt/version.rb +1 -1
  43. data/lib/volt/volt/app.rb +13 -1
  44. data/lib/volt/volt/server_setup/app.rb +19 -1
  45. data/lib/volt/volt/users.rb +11 -22
  46. data/lib/volt.rb +1 -0
  47. data/spec/apps/kitchen_sink/Gemfile +1 -1
  48. data/spec/apps/kitchen_sink/app/main/config/routes.rb +1 -1
  49. data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +22 -0
  50. data/spec/apps/kitchen_sink/app/main/views/main/bindings.html +10 -0
  51. data/spec/apps/kitchen_sink/app/main/views/main/store_demo.html +9 -0
  52. data/spec/controllers/http_controller_spec.rb +27 -0
  53. data/spec/integration/bindings_spec.rb +29 -0
  54. data/spec/integration/store_spec.rb +7 -7
  55. data/spec/models/associations_spec.rb +1 -1
  56. data/spec/models/model_spec.rb +10 -0
  57. data/spec/models/permissions_spec.rb +7 -4
  58. data/spec/reactive/computation_spec.rb +33 -5
  59. data/spec/router/routes_spec.rb +69 -0
  60. data/spec/server/middleware/middleware_handler.rb +24 -0
  61. data/spec/spec_helper.rb +1 -1
  62. data/spec/tasks/user_tasks_spec.rb +3 -2
  63. data/templates/project/Gemfile.tt +2 -2
  64. data/templates/project/config/base/index.html +5 -1
  65. metadata +10 -5
  66. data/spec/apps/kitchen_sink/app/main/views/main/store.html +0 -9
  67. data/templates/project/app/main/models/.empty_directory +0 -0
@@ -19,13 +19,20 @@ module Volt
19
19
  value = @context.instance_eval(&@getter)
20
20
  rescue => e
21
21
  Volt.logger.error("EachBinding Error: #{e.inspect}")
22
+ if RUBY_PLATFORM == 'opal'
23
+ Volt.logger.error(`#{@getter}`)
24
+ else
25
+ Volt.logger.error(e.backtrace.join("\n"))
26
+ end
27
+
22
28
  value = []
23
29
  end
24
30
 
25
31
  value
26
- end.watch_and_resolve! do |value|
27
- update(value)
28
- end
32
+ end.watch_and_resolve!(
33
+ method(:update),
34
+ method(:getter_fail)
35
+ )
29
36
  end
30
37
 
31
38
  # When a changed event happens, we update to the new size.
@@ -64,69 +71,73 @@ module Volt
64
71
  end
65
72
 
66
73
  def item_removed(position)
67
- Volt.run_in_mode(:no_model_promises) do
68
- # Remove dependency
69
- @templates[position].context.locals[:_index_dependency].remove
70
- @templates[position].context.locals["_#{@item_name}_dependency".to_sym].remove
74
+ # Remove dependency
75
+ @templates[position].context.locals[:_index_dependency].remove
76
+ @templates[position].context.locals["_#{@item_name}_dependency".to_sym].remove
71
77
 
72
- @templates[position].remove_anchors
73
- @templates[position].remove
74
- @templates.delete_at(position)
78
+ @templates[position].remove_anchors
79
+ @templates[position].remove
80
+ @templates.delete_at(position)
75
81
 
76
- # Removed at the position, update context for every item after this position
77
- update_indexes_after(position)
78
- end
82
+ # Removed at the position, update context for every item after this position
83
+ update_indexes_after(position)
79
84
  end
80
85
 
81
86
  def item_added(position)
82
- Volt.run_in_mode(:no_model_promises) do
83
- binding_name = @@binding_number
84
- @@binding_number += 1
85
-
86
- if position >= @templates.size
87
- # Setup new bindings in the spot we want to insert the item
88
- dom_section.insert_anchor_before_end(binding_name)
89
- else
90
- # Insert the item before an existing item
91
- dom_section.insert_anchor_before(binding_name, @templates[position].binding_name)
92
- end
87
+ item_context = nil
93
88
 
94
- # TODORW: :parent => @value may change
95
- item_context = SubContext.new({ _index_value: position, parent: @value }, @context)
96
- item_context.locals[@item_name.to_sym] = proc { @value[item_context.locals[:_index_value]] }
89
+ binding_name = @@binding_number
90
+ @@binding_number += 1
97
91
 
98
- position_dependency = Dependency.new
99
- item_context.locals[:_index_dependency] = position_dependency
92
+ if position >= @templates.size
93
+ # Setup new bindings in the spot we want to insert the item
94
+ dom_section.insert_anchor_before_end(binding_name)
95
+ else
96
+ # Insert the item before an existing item
97
+ dom_section.insert_anchor_before(binding_name, @templates[position].binding_name)
98
+ end
100
99
 
101
- # Get and set index
102
- item_context.locals[:_index=] = proc do |val|
103
- position_dependency.changed!
104
- item_context.locals[:_index_value] = val
100
+ # TODORW: :parent => @value may change
101
+ item_context = SubContext.new({ _index_value: position, parent: @value }, @context)
102
+ item_context.locals[@item_name.to_sym] = proc do
103
+ # Fetch only whats there currently, no promises.
104
+ Volt.run_in_mode(:no_model_promises) do
105
+ # puts "GET AT: #{item_context.locals[:_index_value]}"
106
+ @value[item_context.locals[:_index_value]]
105
107
  end
108
+ end
106
109
 
107
- # Get and set value
108
- value_dependency = Dependency.new
109
- item_context.locals["_#{@item_name}_dependency".to_sym] = value_dependency
110
+ position_dependency = Dependency.new
111
+ item_context.locals[:_index_dependency] = position_dependency
110
112
 
111
- item_context.locals["#{@item_name}=".to_sym] = proc do |val|
112
- value_dependency.changed!
113
- @value[item_context.locals[:_index_value]] = val
114
- end
113
+ # Get and set index
114
+ item_context.locals[:_index=] = proc do |val|
115
+ position_dependency.changed!
116
+ item_context.locals[:_index_value] = val
117
+ end
115
118
 
116
- # If the user provides an each_with_index, we can assign the lookup for the index
117
- # variable here.
118
- if @index_name
119
- item_context.locals[@index_name.to_sym] = proc do
120
- position_dependency.depend
121
- item_context.locals[:_index_value]
122
- end
123
- end
119
+ # Get and set value
120
+ value_dependency = Dependency.new
121
+ item_context.locals["_#{@item_name}_dependency".to_sym] = value_dependency
124
122
 
125
- item_template = TemplateRenderer.new(@volt_app, @target, item_context, binding_name, @template_name)
126
- @templates.insert(position, item_template)
123
+ item_context.locals["#{@item_name}=".to_sym] = proc do |val|
124
+ value_dependency.changed!
125
+ @value[item_context.locals[:_index_value]] = val
126
+ end
127
127
 
128
- update_indexes_after(position)
128
+ # If the user provides an each_with_index, we can assign the lookup for the index
129
+ # variable here.
130
+ if @index_name
131
+ item_context.locals[@index_name.to_sym] = proc do
132
+ position_dependency.depend
133
+ item_context.locals[:_index_value]
134
+ end
129
135
  end
136
+
137
+ item_template = TemplateRenderer.new(@volt_app, @target, item_context, binding_name, @template_name)
138
+ @templates.insert(position, item_template)
139
+
140
+ update_indexes_after(position)
130
141
  end
131
142
 
132
143
  # When items are added or removed in the middle of the list, we need
@@ -40,6 +40,20 @@ module Volt
40
40
  # Call the proc the user setup for the event in context,
41
41
  # pass in the wrapper for the JS event
42
42
  result = @context.instance_exec(event, &call_proc)
43
+
44
+ # The following doesn't work due to the promise already chained issue.
45
+ # # Ignore native objects.
46
+ # result = nil unless BasicObject === result
47
+
48
+ # # if the result is a promise, log an exception if it failed and wasn't
49
+ # # handled
50
+ # if result.is_a?(Promise) && !result.next
51
+ # result.fail do |err|
52
+ # Volt.logger.error("EventBinding Error: promise returned from event binding #{@event_name} was rejected")
53
+ # Volt.logger.error(err)
54
+ # end
55
+ # end
56
+
43
57
  end
44
58
 
45
59
  @listener = page.events.add(event_name, self, handler)
@@ -225,7 +225,7 @@ module Volt
225
225
  def call_ready
226
226
  if @controller
227
227
  # Set the current section on the controller if it wants so it can manipulate
228
- # the dom if needed
228
+ # the dom if needed.
229
229
  # Only assign sections for action's, so we don't get AttributeSections bound
230
230
  # also.
231
231
  if @controller.respond_to?(:section=)
@@ -1,7 +1,9 @@
1
+ require 'set'
2
+
1
3
  module Volt
2
4
  class Computation
3
5
  @@current = nil
4
- @@flush_queue = []
6
+ @@flush_queue = Set.new
5
7
 
6
8
  def self.current=(val)
7
9
  @@current = val
@@ -111,7 +113,7 @@ module Volt
111
113
  @@timer = nil
112
114
 
113
115
  computations = @@flush_queue
114
- @@flush_queue = []
116
+ @@flush_queue = Set.new
115
117
 
116
118
  computations.each(&:compute!)
117
119
 
@@ -171,16 +173,12 @@ class Proc
171
173
  end
172
174
 
173
175
  # Does an watch and if the result is a promise, resolves the promise.
174
- # #watch_and_resolve! takes a block that will be passed the resolved results
175
- # of the proc.
176
+ # #watch_and_resolve! takes two procs, one for the promise resolution (then), and
177
+ # one for promise rejection (fail).
176
178
  #
177
179
  # Example:
178
180
  # -> { }
179
- def watch_and_resolve!(yield_nil_for_unresolved_promise=false)
180
- unless block_given?
181
- fail 'watch_and_resolve! requires a block to call when the value is resolved or another value other than a promise is returned in the watch.'
182
- end
183
-
181
+ def watch_and_resolve!(success, failure=nil, yield_nil_for_unresolved_promise=false)
184
182
  # Keep results between runs
185
183
  result = nil
186
184
 
@@ -194,24 +192,35 @@ class Proc
194
192
  # Often you want a to be alerted that an unresolved promise is waiting
195
193
  # to be resolved.
196
194
  if yield_nil_for_unresolved_promise && !result.resolved?
197
- yield(nil)
195
+ success.call(nil)
198
196
  end
199
197
 
200
- result.then do |final|
198
+ # The handler gets called once the promise resolves or is rejected.
199
+ handler = lambda do |&after_handle|
201
200
  # Check to make sure that a new value didn't get reactively pushed
202
201
  # before the promise resolved.
203
202
  if last_promise.is_a?(Promise) && last_promise == result
204
203
  # Don't resolve if the computation was stopped
205
204
  unless comp.stopped?
206
- yield(final)
205
+ # Call the passed in proc
206
+ after_handle.call
207
207
  end
208
208
 
209
209
  # Clear result for GC
210
210
  result = nil
211
211
  end
212
+
213
+ end
214
+
215
+ result.then do |final|
216
+ # Call the success proc passing in the resolved value
217
+ handler.call { success.call(final) }
218
+ end.fail do |err|
219
+ # call the fail callback, passing in the error
220
+ handler.call { failure.call(err) if failure }
212
221
  end
213
222
  else
214
- yield(result)
223
+ success.call(result)
215
224
 
216
225
  # Clear result for GC
217
226
  result = nil
@@ -1,29 +1,5 @@
1
- # Temp until https://github.com/opal/opal/pull/596
2
1
  require 'set'
3
2
 
4
- class Set
5
- def delete(o)
6
- if include?(o)
7
- @hash.delete(o)
8
- true
9
- else
10
- nil
11
- end
12
- end
13
-
14
- def delete_if
15
- block_given? or return enum_for(__method__)
16
- # @hash.delete_if should be faster, but using it breaks the order
17
- # of enumeration in subclasses.
18
- select { |o| yield o }.each { |o| @hash.delete(o) }
19
- self
20
- end
21
-
22
- def to_a
23
- @hash.keys
24
- end
25
- end
26
-
27
3
  module Volt
28
4
  # Dependencies are used to track the current computation so it can be re-run
29
5
  # at a later point if this dependency changes.
@@ -68,6 +68,7 @@ module Volt
68
68
  end
69
69
 
70
70
  # Add server side routes
71
+
71
72
  def get(path, params)
72
73
  create_route(:get, path, params)
73
74
  end
@@ -88,6 +89,35 @@ module Volt
88
89
  create_route(:delete, path, params)
89
90
  end
90
91
 
92
+ #Create rest endpoints
93
+ def rest(path, params)
94
+ endpoints = (params.delete(:only) || [:index, :show, :create, :update, :destroy]).to_a
95
+ endpoints = endpoints - params.delete(:except).to_a
96
+ endpoints.each do |endpoint|
97
+ self.send(('restful_' + endpoint.to_s).to_sym, path, params)
98
+ end
99
+ end
100
+
101
+ def restful_index(base_path, params)
102
+ get(base_path, params.merge(action: 'index'))
103
+ end
104
+
105
+ def restful_create(base_path, params)
106
+ post(base_path, params.merge(action: 'create'))
107
+ end
108
+
109
+ def restful_show(base_path, params)
110
+ get(path_with_id(base_path), params.merge(action: 'show'))
111
+ end
112
+
113
+ def restful_update(base_path, params)
114
+ put(path_with_id(base_path), params.merge(action: 'update'))
115
+ end
116
+
117
+ def restful_destroy(base_path, params)
118
+ delete(path_with_id(base_path), params.merge(action: 'destroy'))
119
+ end
120
+
91
121
  # Takes in params and generates a path and the remaining params
92
122
  # that should be shown in the url. The extra "unused" params
93
123
  # will be tacked onto the end of the url ?param1=value1, etc...
@@ -296,5 +326,10 @@ module Volt
296
326
  def has_binding?(string)
297
327
  string.index('{{') && string.index('}}')
298
328
  end
329
+
330
+ #Append an id to a given path
331
+ def path_with_id(base_path)
332
+ base_path + '/{{ id }}'
333
+ end
299
334
  end
300
335
  end
@@ -59,7 +59,7 @@ module Volt
59
59
  @reader.close
60
60
 
61
61
  volt_app = @server.boot_volt
62
- @rack_app = @server.new_server
62
+ @rack_app = volt_app.middleware
63
63
 
64
64
  # Set the drb object locally
65
65
  @dispatcher = Dispatcher.new(volt_app)
@@ -110,7 +110,19 @@ module Volt
110
110
  end
111
111
  end
112
112
 
113
- def call_on_child(env)
113
+ # When passing an object, Drb will not marshal it if any of its subobjects
114
+ # are not marshalable. So we split the marshable and not marshalbe objects
115
+ # then re-merge them so we get real copies of most values (which are
116
+ # needed in some cases) Then we merge them back into a new hash.
117
+ def call_on_child(env_base, env_other)
118
+ env = env_base
119
+
120
+ # TODO: this requires quite a few trips, there's probably a faster way
121
+ # to handle this.
122
+ env_other.each_pair do |key, value|
123
+ env[key] = value
124
+ end
125
+
114
126
  status, headers, body = @rack_app.call(env)
115
127
 
116
128
  # Extract the body to pass as a string. We need to do this
@@ -138,7 +150,18 @@ module Volt
138
150
  if @exiting
139
151
  [500, {}, 'Server Exiting']
140
152
  else
141
- status, headers, body_str = @server_proxy.call_on_child(env)
153
+ env_base = {}
154
+ env_other = {}
155
+
156
+ env.each_pair do |key, value|
157
+ if [String, TrueClass, FalseClass, Array].include?(value.class)
158
+ env_base.merge!(key => value)
159
+ else
160
+ env_other.merge!(key => value)
161
+ end
162
+ end
163
+
164
+ status, headers, body_str = @server_proxy.call_on_child(env_base, env_other)
142
165
 
143
166
  [status, headers, StringIO.new(body_str)]
144
167
  end
@@ -91,7 +91,7 @@ module Volt
91
91
  begin
92
92
  @message_encoder.send_message(@socket, message)
93
93
  # 'Error: closed stream' comes in sometimes
94
- rescue Errno::ECONNREFUSED, Errno::EPIPE, Error => e
94
+ rescue Errno::ECONNREFUSED, Errno::EPIPE, IOError => e # was also rescuing Error
95
95
  if reconnect!
96
96
  retry
97
97
  else
@@ -34,7 +34,7 @@ module Volt
34
34
  # Register this server as active with the database
35
35
  def register
36
36
  instances = @page.store._active_volt_instances
37
- instances.where(server_id: @server_id).fetch_first do |item|
37
+ instances.where(server_id: @server_id).first.then do |item|
38
38
  ips = local_ips.join(',')
39
39
  time = Time.now.to_i
40
40
  if item
@@ -58,22 +58,26 @@ module Volt
58
58
  attr_reader :server_id, :page
59
59
 
60
60
  def initialize(volt_app)
61
- # Generate a guid
62
- @server_id = SecureRandom.uuid
63
- # The PeerConnection's to peers
64
- @peer_connections = {}
65
- # The server id's for each peer we're connected to
66
- @peer_server_ids = {}
61
+ if Volt::DataStore.fetch.connected?
62
+ # Generate a guid
63
+ @server_id = SecureRandom.uuid
64
+ # The PeerConnection's to peers
65
+ @peer_connections = {}
66
+ # The server id's for each peer we're connected to
67
+ @peer_server_ids = {}
67
68
 
68
- @page = volt_app.page
69
+ @page = volt_app.page
69
70
 
70
- setup_peer_server
71
- start_tracker
71
+ setup_peer_server
72
+ start_tracker
72
73
 
73
- Thread.new do
74
- sleep 1
74
+ Thread.new do
75
+ sleep 1
75
76
 
76
- connect_to_peers
77
+ connect_to_peers
78
+ end
79
+ else
80
+ Volt.logger.error('Unable to connect to the database. Volt will still run, but the message bus requires a database connection to setup connections between nodes, so the message bus has been disabled. This means updates will not be propigated between instances (server, console, runners, etc...)')
77
81
  end
78
82
  end
79
83
 
@@ -110,7 +114,7 @@ module Volt
110
114
  def peers
111
115
  instances = @page.store._active_volt_instances
112
116
 
113
- instances.where(server_id: {'$ne' => @server_id}).fetch.sync
117
+ instances.where(server_id: {'$ne' => @server_id}).all.sync
114
118
  end
115
119
 
116
120
  def connect_to_peers
@@ -118,13 +122,16 @@ module Volt
118
122
  # Start connecting to all at the same time. Since most will connect or
119
123
  # timeout, this is the desired behaviour.
120
124
  Thread.new do
121
- peer_connection = PeerConnection.connect_to(self, peer._ips, peer._port)
122
-
123
- if peer_connection
124
- add_peer_connection(peer_connection)
125
- else
126
- # remove if not alive anymore.
127
- still_alive?(peer._server_id)
125
+ # sometimes we get nil peers for some reason
126
+ if peer
127
+ peer_connection = PeerConnection.connect_to(self, peer._ips, peer._port)
128
+
129
+ if peer_connection
130
+ add_peer_connection(peer_connection)
131
+ else
132
+ # remove if not alive anymore.
133
+ still_alive?(peer._server_id)
134
+ end
128
135
  end
129
136
  end
130
137
  end
@@ -177,7 +184,7 @@ module Volt
177
184
  # Unable to write to the socket, retry until the instance is no
178
185
  # longer marking its self as active in the database
179
186
  peer_table = @page.store._active_volt_instances
180
- peer = peer_table.where(server_id: peer_server_id).fetch_first.sync
187
+ peer = peer_table.where(server_id: peer_server_id).first.sync
181
188
  if peer
182
189
  # Found the peer, retry if it has reported in in the last 2
183
190
  # minutes.
@@ -0,0 +1,67 @@
1
+ # Responsible for setting up all "out of the box" middleware on a Volt app.
2
+
3
+ require 'rack'
4
+ require 'volt/server/rack/keep_alive'
5
+ require 'volt/server/rack/quiet_common_logger'
6
+ require 'volt/server/rack/opal_files'
7
+ require 'volt/server/rack/index_files'
8
+ require 'volt/server/rack/http_resource'
9
+
10
+
11
+
12
+ module Volt
13
+ class DefaultMiddlewareStack
14
+ # Setup on the middleware we can setup before booting components
15
+ def self.preboot_setup(volt_app, rack_app)
16
+ # Should only be used in production
17
+ if Volt.config.deflate
18
+ rack_app.use Rack::Deflater
19
+ rack_app.use Rack::Chunked
20
+ end
21
+
22
+ rack_app.use Rack::ContentLength
23
+ rack_app.use Rack::KeepAlive
24
+ rack_app.use Rack::ConditionalGet
25
+ rack_app.use Rack::ETag
26
+
27
+ rack_app.use Rack::Session::Cookie, {
28
+ :key => 'rack.session',
29
+ # :domain => 'localhost.com',
30
+ :path => '/',
31
+ :expire_after => 2592000,
32
+ :secret => Volt.config.app_secret
33
+ }
34
+
35
+ rack_app.use QuietCommonLogger
36
+ rack_app.use Rack::ShowExceptions
37
+ end
38
+
39
+ # Setup the middleware that we need to wait for components to boot before we
40
+ # can set them up.
41
+ def self.postboot_setup(volt_app, rack_app)
42
+ component_paths = volt_app.component_paths
43
+ rack_app.map '/components' do
44
+ run ComponentHandler.new(component_paths)
45
+ end
46
+
47
+ # Serve the opal files
48
+ opal_files = OpalFiles.new(rack_app, volt_app.app_path, volt_app.component_paths)
49
+
50
+ # Serve the main html files from public, also figure out
51
+ # which JS/CSS files to serve.
52
+ rack_app.use IndexFiles, volt_app, volt_app.component_paths, opal_files
53
+
54
+ rack_app.use HttpResource, volt_app, volt_app.router
55
+
56
+ rack_app.use Rack::Static,
57
+ urls: ['/'],
58
+ root: 'config/base',
59
+ index: '',
60
+ header_rules: [
61
+ [:all, { 'Cache-Control' => 'public, max-age=86400' }]
62
+ ]
63
+
64
+ rack_app.run lambda { |env| [404, { 'Content-Type' => 'text/html; charset=utf-8' }, ['404 - page not found']] }
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,58 @@
1
+ # Volt::MiddlewareStack provides an interface where app code can add custom
2
+ # rack middleware. Volt.current_app.middleware returns an instance of
3
+ # Volt::MiddlewareStack, and apps can call #use to add in more middleware.
4
+
5
+ module Volt
6
+ class MiddlewareStack
7
+ attr_reader :middlewares
8
+
9
+ def initialize
10
+ # Setup the next app
11
+ @middlewares = []
12
+ @maps = []
13
+ end
14
+
15
+ # Set the app that gets called after the middleware runs
16
+ # def set_app(app)
17
+ # @app = app
18
+ # end
19
+
20
+ def use(*args, &block)
21
+ @middlewares << [args, block]
22
+
23
+ # invalidate builder, so it gets built again
24
+ @builder = nil
25
+ end
26
+
27
+ def map(path, &block)
28
+ @maps << [path, block]
29
+ end
30
+
31
+ def run(app)
32
+ @app = app
33
+ end
34
+
35
+ # Builds a new Rack::Builder with the middleware and the app
36
+ def build
37
+ @builder = Rack::Builder.new
38
+
39
+ @maps.each do |path, block|
40
+ @builder.map(path, &block)
41
+ end
42
+
43
+ @middlewares.each do |middleware|
44
+ @builder.use(*middleware[0], &middleware[1])
45
+ end
46
+
47
+ @builder.run(@app)
48
+ end
49
+
50
+ def call(env)
51
+ unless @builder
52
+ build
53
+ end
54
+
55
+ @builder.call(env)
56
+ end
57
+ end
58
+ end
@@ -5,7 +5,7 @@ module Volt
5
5
  # A request object for a HttpController. See Rack::Request for more details
6
6
  class HttpRequest < Rack::Request
7
7
  # Returns the request format
8
- # /blub/index.html => html
8
+ # /acticles/index.html => html
9
9
  # Defaults to the media_type of the request
10
10
  def format
11
11
  path_format || media_type
@@ -40,6 +40,13 @@ module Volt
40
40
  namespace_module = Object.const_get(namespace.camelize.to_sym)
41
41
  klass = namespace_module.const_get(controller_name.camelize.to_sym)
42
42
  controller = klass.new(@volt_app, params, request)
43
+
44
+ # Use the 'meta' thread local to set the user_id for Volt.current_user
45
+ meta_data = {}
46
+ user_id = request.cookies['user_id']
47
+ meta_data['user_id'] = user_id if user_id
48
+ Thread.current['meta'] = meta_data
49
+
43
50
  controller.perform(action)
44
51
  end
45
52
  end
@@ -0,0 +1,20 @@
1
+ module Rack
2
+ # TODO: For some reason in Rack (or maybe thin), 304 headers close
3
+ # the http connection. We might need to make this check if keep
4
+ # alive was in the request.
5
+ class KeepAlive
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ status, headers, body = @app.call(env)
12
+
13
+ if status == 304 && env['HTTP_CONNECTION'] && env['HTTP_CONNECTION'].downcase == 'keep-alive'
14
+ headers['Connection'] = 'keep-alive'
15
+ end
16
+
17
+ [status, headers, body]
18
+ end
19
+ end
20
+ end