halorgium-actionpack 3.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +5179 -0
- data/MIT-LICENSE +21 -0
- data/README +409 -0
- data/lib/abstract_controller.rb +16 -0
- data/lib/abstract_controller/base.rb +158 -0
- data/lib/abstract_controller/callbacks.rb +113 -0
- data/lib/abstract_controller/exceptions.rb +12 -0
- data/lib/abstract_controller/helpers.rb +151 -0
- data/lib/abstract_controller/layouts.rb +250 -0
- data/lib/abstract_controller/localized_cache.rb +49 -0
- data/lib/abstract_controller/logger.rb +61 -0
- data/lib/abstract_controller/rendering_controller.rb +188 -0
- data/lib/action_controller.rb +72 -0
- data/lib/action_controller/base.rb +168 -0
- data/lib/action_controller/caching.rb +80 -0
- data/lib/action_controller/caching/actions.rb +163 -0
- data/lib/action_controller/caching/fragments.rb +116 -0
- data/lib/action_controller/caching/pages.rb +154 -0
- data/lib/action_controller/caching/sweeping.rb +97 -0
- data/lib/action_controller/deprecated.rb +4 -0
- data/lib/action_controller/deprecated/integration_test.rb +2 -0
- data/lib/action_controller/deprecated/performance_test.rb +1 -0
- data/lib/action_controller/dispatch/dispatcher.rb +57 -0
- data/lib/action_controller/metal.rb +129 -0
- data/lib/action_controller/metal/benchmarking.rb +73 -0
- data/lib/action_controller/metal/compatibility.rb +145 -0
- data/lib/action_controller/metal/conditional_get.rb +86 -0
- data/lib/action_controller/metal/configuration.rb +28 -0
- data/lib/action_controller/metal/cookies.rb +105 -0
- data/lib/action_controller/metal/exceptions.rb +55 -0
- data/lib/action_controller/metal/filter_parameter_logging.rb +77 -0
- data/lib/action_controller/metal/flash.rb +162 -0
- data/lib/action_controller/metal/head.rb +27 -0
- data/lib/action_controller/metal/helpers.rb +115 -0
- data/lib/action_controller/metal/hide_actions.rb +47 -0
- data/lib/action_controller/metal/http_authentication.rb +312 -0
- data/lib/action_controller/metal/layouts.rb +171 -0
- data/lib/action_controller/metal/mime_responds.rb +317 -0
- data/lib/action_controller/metal/rack_convenience.rb +27 -0
- data/lib/action_controller/metal/redirector.rb +22 -0
- data/lib/action_controller/metal/render_options.rb +103 -0
- data/lib/action_controller/metal/rendering_controller.rb +57 -0
- data/lib/action_controller/metal/request_forgery_protection.rb +108 -0
- data/lib/action_controller/metal/rescuable.rb +13 -0
- data/lib/action_controller/metal/responder.rb +200 -0
- data/lib/action_controller/metal/session.rb +15 -0
- data/lib/action_controller/metal/session_management.rb +45 -0
- data/lib/action_controller/metal/streaming.rb +188 -0
- data/lib/action_controller/metal/testing.rb +39 -0
- data/lib/action_controller/metal/url_for.rb +41 -0
- data/lib/action_controller/metal/verification.rb +130 -0
- data/lib/action_controller/middleware.rb +38 -0
- data/lib/action_controller/notifications.rb +10 -0
- data/lib/action_controller/polymorphic_routes.rb +183 -0
- data/lib/action_controller/record_identifier.rb +91 -0
- data/lib/action_controller/testing/process.rb +111 -0
- data/lib/action_controller/testing/test_case.rb +345 -0
- data/lib/action_controller/translation.rb +13 -0
- data/lib/action_controller/url_rewriter.rb +204 -0
- data/lib/action_controller/vendor/html-scanner.rb +16 -0
- data/lib/action_controller/vendor/html-scanner/html/document.rb +68 -0
- data/lib/action_controller/vendor/html-scanner/html/node.rb +537 -0
- data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +176 -0
- data/lib/action_controller/vendor/html-scanner/html/selector.rb +828 -0
- data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +105 -0
- data/lib/action_controller/vendor/html-scanner/html/version.rb +11 -0
- data/lib/action_dispatch.rb +70 -0
- data/lib/action_dispatch/http/headers.rb +33 -0
- data/lib/action_dispatch/http/mime_type.rb +231 -0
- data/lib/action_dispatch/http/mime_types.rb +23 -0
- data/lib/action_dispatch/http/request.rb +539 -0
- data/lib/action_dispatch/http/response.rb +290 -0
- data/lib/action_dispatch/http/status_codes.rb +42 -0
- data/lib/action_dispatch/http/utils.rb +20 -0
- data/lib/action_dispatch/middleware/callbacks.rb +50 -0
- data/lib/action_dispatch/middleware/params_parser.rb +79 -0
- data/lib/action_dispatch/middleware/rescue.rb +26 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +208 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +235 -0
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +47 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +143 -0
- data/lib/action_dispatch/middleware/stack.rb +116 -0
- data/lib/action_dispatch/middleware/static.rb +44 -0
- data/lib/action_dispatch/middleware/string_coercion.rb +29 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +24 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +26 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +10 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +29 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +2 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +10 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +21 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +2 -0
- data/lib/action_dispatch/routing.rb +381 -0
- data/lib/action_dispatch/routing/deprecated_mapper.rb +878 -0
- data/lib/action_dispatch/routing/mapper.rb +327 -0
- data/lib/action_dispatch/routing/route.rb +49 -0
- data/lib/action_dispatch/routing/route_set.rb +497 -0
- data/lib/action_dispatch/testing/assertions.rb +8 -0
- data/lib/action_dispatch/testing/assertions/dom.rb +35 -0
- data/lib/action_dispatch/testing/assertions/model.rb +19 -0
- data/lib/action_dispatch/testing/assertions/response.rb +145 -0
- data/lib/action_dispatch/testing/assertions/routing.rb +144 -0
- data/lib/action_dispatch/testing/assertions/selector.rb +639 -0
- data/lib/action_dispatch/testing/assertions/tag.rb +123 -0
- data/lib/action_dispatch/testing/integration.rb +504 -0
- data/lib/action_dispatch/testing/performance_test.rb +15 -0
- data/lib/action_dispatch/testing/test_request.rb +83 -0
- data/lib/action_dispatch/testing/test_response.rb +131 -0
- data/lib/action_pack.rb +24 -0
- data/lib/action_pack/version.rb +9 -0
- data/lib/action_view.rb +58 -0
- data/lib/action_view/base.rb +308 -0
- data/lib/action_view/context.rb +44 -0
- data/lib/action_view/erb/util.rb +48 -0
- data/lib/action_view/helpers.rb +62 -0
- data/lib/action_view/helpers/active_model_helper.rb +306 -0
- data/lib/action_view/helpers/ajax_helper.rb +68 -0
- data/lib/action_view/helpers/asset_tag_helper.rb +830 -0
- data/lib/action_view/helpers/atom_feed_helper.rb +198 -0
- data/lib/action_view/helpers/cache_helper.rb +39 -0
- data/lib/action_view/helpers/capture_helper.rb +168 -0
- data/lib/action_view/helpers/date_helper.rb +988 -0
- data/lib/action_view/helpers/debug_helper.rb +38 -0
- data/lib/action_view/helpers/form_helper.rb +1102 -0
- data/lib/action_view/helpers/form_options_helper.rb +600 -0
- data/lib/action_view/helpers/form_tag_helper.rb +495 -0
- data/lib/action_view/helpers/javascript_helper.rb +208 -0
- data/lib/action_view/helpers/number_helper.rb +311 -0
- data/lib/action_view/helpers/prototype_helper.rb +1309 -0
- data/lib/action_view/helpers/raw_output_helper.rb +9 -0
- data/lib/action_view/helpers/record_identification_helper.rb +20 -0
- data/lib/action_view/helpers/record_tag_helper.rb +58 -0
- data/lib/action_view/helpers/sanitize_helper.rb +259 -0
- data/lib/action_view/helpers/scriptaculous_helper.rb +226 -0
- data/lib/action_view/helpers/tag_helper.rb +151 -0
- data/lib/action_view/helpers/text_helper.rb +594 -0
- data/lib/action_view/helpers/translation_helper.rb +39 -0
- data/lib/action_view/helpers/url_helper.rb +639 -0
- data/lib/action_view/locale/en.yml +117 -0
- data/lib/action_view/paths.rb +80 -0
- data/lib/action_view/render/partials.rb +342 -0
- data/lib/action_view/render/rendering.rb +134 -0
- data/lib/action_view/safe_buffer.rb +28 -0
- data/lib/action_view/template/error.rb +101 -0
- data/lib/action_view/template/handler.rb +36 -0
- data/lib/action_view/template/handlers.rb +52 -0
- data/lib/action_view/template/handlers/builder.rb +17 -0
- data/lib/action_view/template/handlers/erb.rb +53 -0
- data/lib/action_view/template/handlers/rjs.rb +18 -0
- data/lib/action_view/template/resolver.rb +165 -0
- data/lib/action_view/template/template.rb +131 -0
- data/lib/action_view/template/text.rb +38 -0
- data/lib/action_view/test_case.rb +163 -0
- metadata +236 -0
@@ -0,0 +1,327 @@
|
|
1
|
+
module ActionDispatch
|
2
|
+
module Routing
|
3
|
+
class Mapper
|
4
|
+
module Resources
|
5
|
+
def resource(*resources, &block)
|
6
|
+
options = resources.last.is_a?(Hash) ? resources.pop : {}
|
7
|
+
|
8
|
+
if resources.length > 1
|
9
|
+
raise ArgumentError if block_given?
|
10
|
+
resources.each { |r| resource(r, options) }
|
11
|
+
return self
|
12
|
+
end
|
13
|
+
|
14
|
+
resource = resources.pop
|
15
|
+
|
16
|
+
if @scope[:scope_level] == :resources
|
17
|
+
member do
|
18
|
+
resource(resource, options, &block)
|
19
|
+
end
|
20
|
+
return self
|
21
|
+
end
|
22
|
+
|
23
|
+
singular = resource.to_s
|
24
|
+
plural = singular.pluralize
|
25
|
+
|
26
|
+
controller(plural) do
|
27
|
+
namespace(resource) do
|
28
|
+
with_scope_level(:resource) do
|
29
|
+
yield if block_given?
|
30
|
+
|
31
|
+
get "", :to => :show, :as => "#{singular}"
|
32
|
+
post "", :to => :create
|
33
|
+
put "", :to => :update
|
34
|
+
delete "", :to => :destroy
|
35
|
+
get "new", :to => :new, :as => "new_#{singular}"
|
36
|
+
get "edit", :to => :edit, :as => "edit_#{singular}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def resources(*resources, &block)
|
45
|
+
options = resources.last.is_a?(Hash) ? resources.pop : {}
|
46
|
+
|
47
|
+
if resources.length > 1
|
48
|
+
raise ArgumentError if block_given?
|
49
|
+
resources.each { |r| resources(r, options) }
|
50
|
+
return self
|
51
|
+
end
|
52
|
+
|
53
|
+
resource = resources.pop
|
54
|
+
|
55
|
+
if @scope[:scope_level] == :resources
|
56
|
+
member do
|
57
|
+
resources(resource, options, &block)
|
58
|
+
end
|
59
|
+
return self
|
60
|
+
end
|
61
|
+
|
62
|
+
plural = resource.to_s
|
63
|
+
singular = plural.singularize
|
64
|
+
|
65
|
+
controller(resource) do
|
66
|
+
namespace(resource) do
|
67
|
+
with_scope_level(:resources) do
|
68
|
+
yield if block_given?
|
69
|
+
|
70
|
+
member do
|
71
|
+
get "", :to => :show, :as => "#{singular}"
|
72
|
+
put "", :to => :update
|
73
|
+
delete "", :to => :destroy
|
74
|
+
get "edit", :to => :edit, :as => "edit_#{singular}"
|
75
|
+
end
|
76
|
+
|
77
|
+
collection do
|
78
|
+
get "", :to => :index, :as => "#{plural}"
|
79
|
+
post "", :to => :create
|
80
|
+
get "new", :to => :new, :as => "new_#{singular}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
def collection
|
90
|
+
unless @scope[:scope_level] == :resources
|
91
|
+
raise ArgumentError, "can't use collection outside resources scope"
|
92
|
+
end
|
93
|
+
|
94
|
+
with_scope_level(:collection) do
|
95
|
+
yield
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def member
|
100
|
+
unless @scope[:scope_level] == :resources
|
101
|
+
raise ArgumentError, "can't use member outside resources scope"
|
102
|
+
end
|
103
|
+
|
104
|
+
with_scope_level(:member) do
|
105
|
+
scope(":id") do
|
106
|
+
yield
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def match(*args)
|
112
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
113
|
+
args.push(options)
|
114
|
+
|
115
|
+
case options.delete(:on)
|
116
|
+
when :collection
|
117
|
+
return collection { match(*args) }
|
118
|
+
when :member
|
119
|
+
return member { match(*args) }
|
120
|
+
end
|
121
|
+
|
122
|
+
if @scope[:scope_level] == :resources
|
123
|
+
raise ArgumentError, "can't define route directly in resources scope"
|
124
|
+
end
|
125
|
+
|
126
|
+
super
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
def with_scope_level(kind)
|
131
|
+
old, @scope[:scope_level] = @scope[:scope_level], kind
|
132
|
+
yield
|
133
|
+
ensure
|
134
|
+
@scope[:scope_level] = old
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
module Scoping
|
139
|
+
def scope(*args)
|
140
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
141
|
+
|
142
|
+
constraints = options.delete(:constraints) || {}
|
143
|
+
unless constraints.is_a?(Hash)
|
144
|
+
block, constraints = constraints, {}
|
145
|
+
end
|
146
|
+
constraints, @scope[:constraints] = @scope[:constraints], (@scope[:constraints] || {}).merge(constraints)
|
147
|
+
blocks, @scope[:blocks] = @scope[:blocks], (@scope[:blocks] || []) + [block]
|
148
|
+
|
149
|
+
options, @scope[:options] = @scope[:options], (@scope[:options] || {}).merge(options)
|
150
|
+
|
151
|
+
path_set = controller_set = false
|
152
|
+
|
153
|
+
case args.first
|
154
|
+
when String
|
155
|
+
path_set = true
|
156
|
+
path = args.first
|
157
|
+
path, @scope[:path] = @scope[:path], "#{@scope[:path]}#{Rack::Mount::Utils.normalize_path(path)}"
|
158
|
+
when Symbol
|
159
|
+
controller_set = true
|
160
|
+
controller = args.first
|
161
|
+
controller, @scope[:controller] = @scope[:controller], controller
|
162
|
+
end
|
163
|
+
|
164
|
+
yield
|
165
|
+
|
166
|
+
self
|
167
|
+
ensure
|
168
|
+
@scope[:path] = path if path_set
|
169
|
+
@scope[:controller] = controller if controller_set
|
170
|
+
@scope[:options] = options
|
171
|
+
@scope[:blocks] = blocks
|
172
|
+
@scope[:constraints] = constraints
|
173
|
+
end
|
174
|
+
|
175
|
+
def controller(controller)
|
176
|
+
scope(controller.to_sym) { yield }
|
177
|
+
end
|
178
|
+
|
179
|
+
def namespace(path)
|
180
|
+
scope(path.to_s) { yield }
|
181
|
+
end
|
182
|
+
|
183
|
+
def constraints(constraints = {})
|
184
|
+
scope(:constraints => constraints) { yield }
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
class Constraints
|
189
|
+
def initialize(app, constraints = [])
|
190
|
+
@app, @constraints = app, constraints
|
191
|
+
end
|
192
|
+
|
193
|
+
def call(env)
|
194
|
+
req = Rack::Request.new(env)
|
195
|
+
|
196
|
+
@constraints.each { |constraint|
|
197
|
+
if constraint.respond_to?(:matches?) && !constraint.matches?(req)
|
198
|
+
return Rack::Mount::Const::EXPECTATION_FAILED_RESPONSE
|
199
|
+
elsif constraint.respond_to?(:call) && !constraint.call(req)
|
200
|
+
return Rack::Mount::Const::EXPECTATION_FAILED_RESPONSE
|
201
|
+
end
|
202
|
+
}
|
203
|
+
|
204
|
+
@app.call(env)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def initialize(set)
|
209
|
+
@set = set
|
210
|
+
@scope = {}
|
211
|
+
|
212
|
+
extend Scoping
|
213
|
+
extend Resources
|
214
|
+
end
|
215
|
+
|
216
|
+
def get(*args, &block)
|
217
|
+
map_method(:get, *args, &block)
|
218
|
+
end
|
219
|
+
|
220
|
+
def post(*args, &block)
|
221
|
+
map_method(:post, *args, &block)
|
222
|
+
end
|
223
|
+
|
224
|
+
def put(*args, &block)
|
225
|
+
map_method(:put, *args, &block)
|
226
|
+
end
|
227
|
+
|
228
|
+
def delete(*args, &block)
|
229
|
+
map_method(:delete, *args, &block)
|
230
|
+
end
|
231
|
+
|
232
|
+
def root(options = {})
|
233
|
+
match '/', options.merge(:as => :root)
|
234
|
+
end
|
235
|
+
|
236
|
+
def match(*args)
|
237
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
238
|
+
|
239
|
+
if args.length > 1
|
240
|
+
args.each { |path| match(path, options) }
|
241
|
+
return self
|
242
|
+
end
|
243
|
+
|
244
|
+
if args.first.is_a?(Symbol)
|
245
|
+
return match(args.first.to_s, options.merge(:to => args.first.to_sym))
|
246
|
+
end
|
247
|
+
|
248
|
+
path = args.first
|
249
|
+
|
250
|
+
options = (@scope[:options] || {}).merge(options)
|
251
|
+
conditions, defaults = {}, {}
|
252
|
+
|
253
|
+
path = nil if path == ""
|
254
|
+
path = Rack::Mount::Utils.normalize_path(path) if path
|
255
|
+
path = "#{@scope[:path]}#{path}" if @scope[:path]
|
256
|
+
|
257
|
+
raise ArgumentError, "path is required" unless path
|
258
|
+
|
259
|
+
constraints = options[:constraints] || {}
|
260
|
+
unless constraints.is_a?(Hash)
|
261
|
+
block, constraints = constraints, {}
|
262
|
+
end
|
263
|
+
blocks = ((@scope[:blocks] || []) + [block]).compact
|
264
|
+
constraints = (@scope[:constraints] || {}).merge(constraints)
|
265
|
+
options.each { |k, v| constraints[k] = v if v.is_a?(Regexp) }
|
266
|
+
|
267
|
+
conditions[:path_info] = path
|
268
|
+
requirements = constraints.dup
|
269
|
+
|
270
|
+
path_regexp = Rack::Mount::Strexp.compile(path, constraints, SEPARATORS)
|
271
|
+
segment_keys = Rack::Mount::RegexpWithNamedGroups.new(path_regexp).names
|
272
|
+
constraints.reject! { |k, v| segment_keys.include?(k.to_s) }
|
273
|
+
conditions.merge!(constraints)
|
274
|
+
|
275
|
+
if via = options[:via]
|
276
|
+
via = Array(via).map { |m| m.to_s.upcase }
|
277
|
+
conditions[:request_method] = Regexp.union(*via)
|
278
|
+
end
|
279
|
+
|
280
|
+
defaults[:controller] = @scope[:controller].to_s if @scope[:controller]
|
281
|
+
|
282
|
+
if options[:to].respond_to?(:call)
|
283
|
+
app = options[:to]
|
284
|
+
defaults.delete(:controller)
|
285
|
+
defaults.delete(:action)
|
286
|
+
elsif options[:to].is_a?(String)
|
287
|
+
defaults[:controller], defaults[:action] = options[:to].split('#')
|
288
|
+
elsif options[:to].is_a?(Symbol)
|
289
|
+
defaults[:action] = options[:to].to_s
|
290
|
+
end
|
291
|
+
app ||= Routing::RouteSet::Dispatcher.new(:defaults => defaults)
|
292
|
+
|
293
|
+
if app.is_a?(Routing::RouteSet::Dispatcher)
|
294
|
+
unless defaults.include?(:controller) || segment_keys.include?("controller")
|
295
|
+
raise ArgumentError, "missing :controller"
|
296
|
+
end
|
297
|
+
unless defaults.include?(:action) || segment_keys.include?("action")
|
298
|
+
raise ArgumentError, "missing :action"
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
app = Constraints.new(app, blocks) if blocks.any?
|
303
|
+
@set.add_route(app, conditions, requirements, defaults, options[:as])
|
304
|
+
|
305
|
+
self
|
306
|
+
end
|
307
|
+
|
308
|
+
def redirect(path, options = {})
|
309
|
+
status = options[:status] || 301
|
310
|
+
lambda { |env|
|
311
|
+
req = Rack::Request.new(env)
|
312
|
+
url = req.scheme + '://' + req.host + path
|
313
|
+
[status, {'Location' => url, 'Content-Type' => 'text/html'}, ['Moved Permanently']]
|
314
|
+
}
|
315
|
+
end
|
316
|
+
|
317
|
+
private
|
318
|
+
def map_method(method, *args, &block)
|
319
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
320
|
+
options[:via] = method
|
321
|
+
args.push(options)
|
322
|
+
match(*args, &block)
|
323
|
+
self
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module ActionDispatch
|
2
|
+
module Routing
|
3
|
+
class Route #:nodoc:
|
4
|
+
attr_reader :app, :conditions, :defaults, :name
|
5
|
+
attr_reader :path, :requirements
|
6
|
+
|
7
|
+
def initialize(app, conditions = {}, requirements = {}, defaults = {}, name = nil)
|
8
|
+
@app = app
|
9
|
+
@defaults = defaults
|
10
|
+
@name = name
|
11
|
+
|
12
|
+
@requirements = requirements.merge(defaults)
|
13
|
+
@requirements.delete(:controller) if @requirements[:controller].is_a?(Regexp)
|
14
|
+
@requirements.delete_if { |k, v|
|
15
|
+
v == Regexp.compile("[^#{SEPARATORS.join}]+")
|
16
|
+
}
|
17
|
+
|
18
|
+
if path = conditions[:path_info]
|
19
|
+
@path = path
|
20
|
+
conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS)
|
21
|
+
end
|
22
|
+
|
23
|
+
@conditions = conditions.inject({}) { |h, (k, v)|
|
24
|
+
h[k] = Rack::Mount::RegexpWithNamedGroups.new(v)
|
25
|
+
h
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def verb
|
30
|
+
if method = conditions[:request_method]
|
31
|
+
case method
|
32
|
+
when Regexp
|
33
|
+
method.source.upcase
|
34
|
+
else
|
35
|
+
method.to_s.upcase
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def segment_keys
|
41
|
+
@segment_keys ||= conditions[:path_info].names.compact.map { |key| key.to_sym }
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_a
|
45
|
+
[@app, @conditions, @defaults, @name]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,497 @@
|
|
1
|
+
require 'rack/mount'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module ActionDispatch
|
5
|
+
module Routing
|
6
|
+
class RouteSet #:nodoc:
|
7
|
+
NotFound = lambda { |env|
|
8
|
+
raise ActionController::RoutingError, "No route matches #{env[::Rack::Mount::Const::PATH_INFO].inspect} with #{env.inspect}"
|
9
|
+
}
|
10
|
+
|
11
|
+
PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
|
12
|
+
|
13
|
+
class Dispatcher
|
14
|
+
def initialize(options = {})
|
15
|
+
defaults = options[:defaults]
|
16
|
+
@glob_param = options.delete(:glob)
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(env)
|
20
|
+
params = env[PARAMETERS_KEY]
|
21
|
+
merge_default_action!(params)
|
22
|
+
split_glob_param!(params) if @glob_param
|
23
|
+
params.each do |key, value|
|
24
|
+
if value.is_a?(String)
|
25
|
+
value = value.dup.force_encoding(Encoding::BINARY) if value.respond_to?(:force_encoding)
|
26
|
+
params[key] = URI.unescape(value)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
if env['action_controller.recognize']
|
31
|
+
[200, {}, params]
|
32
|
+
else
|
33
|
+
controller = controller(params)
|
34
|
+
controller.action(params[:action]).call(env)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def controller(params)
|
40
|
+
if params && params.has_key?(:controller)
|
41
|
+
controller = "#{params[:controller].camelize}Controller"
|
42
|
+
ActiveSupport::Inflector.constantize(controller)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def merge_default_action!(params)
|
47
|
+
params[:action] ||= 'index'
|
48
|
+
end
|
49
|
+
|
50
|
+
def split_glob_param!(params)
|
51
|
+
params[@glob_param] = params[@glob_param].split('/').map { |v| URI.unescape(v) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
# A NamedRouteCollection instance is a collection of named routes, and also
|
57
|
+
# maintains an anonymous module that can be used to install helpers for the
|
58
|
+
# named routes.
|
59
|
+
class NamedRouteCollection #:nodoc:
|
60
|
+
include Enumerable
|
61
|
+
attr_reader :routes, :helpers
|
62
|
+
|
63
|
+
def initialize
|
64
|
+
clear!
|
65
|
+
end
|
66
|
+
|
67
|
+
def clear!
|
68
|
+
@routes = {}
|
69
|
+
@helpers = []
|
70
|
+
|
71
|
+
@module ||= Module.new
|
72
|
+
@module.instance_methods.each do |selector|
|
73
|
+
@module.class_eval { remove_method selector }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def add(name, route)
|
78
|
+
routes[name.to_sym] = route
|
79
|
+
define_named_route_methods(name, route)
|
80
|
+
end
|
81
|
+
|
82
|
+
def get(name)
|
83
|
+
routes[name.to_sym]
|
84
|
+
end
|
85
|
+
|
86
|
+
alias []= add
|
87
|
+
alias [] get
|
88
|
+
alias clear clear!
|
89
|
+
|
90
|
+
def each
|
91
|
+
routes.each { |name, route| yield name, route }
|
92
|
+
self
|
93
|
+
end
|
94
|
+
|
95
|
+
def names
|
96
|
+
routes.keys
|
97
|
+
end
|
98
|
+
|
99
|
+
def length
|
100
|
+
routes.length
|
101
|
+
end
|
102
|
+
|
103
|
+
def reset!
|
104
|
+
old_routes = routes.dup
|
105
|
+
clear!
|
106
|
+
old_routes.each do |name, route|
|
107
|
+
add(name, route)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false)
|
112
|
+
reset! if regenerate
|
113
|
+
Array(destinations).each do |dest|
|
114
|
+
dest.__send__(:include, @module)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
def url_helper_name(name, kind = :url)
|
120
|
+
:"#{name}_#{kind}"
|
121
|
+
end
|
122
|
+
|
123
|
+
def hash_access_name(name, kind = :url)
|
124
|
+
:"hash_for_#{name}_#{kind}"
|
125
|
+
end
|
126
|
+
|
127
|
+
def define_named_route_methods(name, route)
|
128
|
+
{:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts|
|
129
|
+
hash = route.defaults.merge(:use_route => name).merge(opts)
|
130
|
+
define_hash_access route, name, kind, hash
|
131
|
+
define_url_helper route, name, kind, hash
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def named_helper_module_eval(code, *args)
|
136
|
+
@module.module_eval(code, *args)
|
137
|
+
end
|
138
|
+
|
139
|
+
def define_hash_access(route, name, kind, options)
|
140
|
+
selector = hash_access_name(name, kind)
|
141
|
+
named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
|
142
|
+
def #{selector}(options = nil) # def hash_for_users_url(options = nil)
|
143
|
+
options ? #{options.inspect}.merge(options) : #{options.inspect} # options ? {:only_path=>false}.merge(options) : {:only_path=>false}
|
144
|
+
end # end
|
145
|
+
protected :#{selector} # protected :hash_for_users_url
|
146
|
+
end_eval
|
147
|
+
helpers << selector
|
148
|
+
end
|
149
|
+
|
150
|
+
def define_url_helper(route, name, kind, options)
|
151
|
+
selector = url_helper_name(name, kind)
|
152
|
+
# The segment keys used for positional parameters
|
153
|
+
|
154
|
+
hash_access_method = hash_access_name(name, kind)
|
155
|
+
|
156
|
+
# allow ordered parameters to be associated with corresponding
|
157
|
+
# dynamic segments, so you can do
|
158
|
+
#
|
159
|
+
# foo_url(bar, baz, bang)
|
160
|
+
#
|
161
|
+
# instead of
|
162
|
+
#
|
163
|
+
# foo_url(:bar => bar, :baz => baz, :bang => bang)
|
164
|
+
#
|
165
|
+
# Also allow options hash, so you can do
|
166
|
+
#
|
167
|
+
# foo_url(bar, baz, bang, :sort_by => 'baz')
|
168
|
+
#
|
169
|
+
named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
|
170
|
+
def #{selector}(*args) # def users_url(*args)
|
171
|
+
#
|
172
|
+
opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first
|
173
|
+
args.first || {} # args.first || {}
|
174
|
+
else # else
|
175
|
+
options = args.extract_options! # options = args.extract_options!
|
176
|
+
args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| # args = args.zip([]).inject({}) do |h, (v, k)|
|
177
|
+
h[k] = v # h[k] = v
|
178
|
+
h # h
|
179
|
+
end # end
|
180
|
+
options.merge(args) # options.merge(args)
|
181
|
+
end # end
|
182
|
+
#
|
183
|
+
url_for(#{hash_access_method}(opts)) # url_for(hash_for_users_url(opts))
|
184
|
+
#
|
185
|
+
end # end
|
186
|
+
#Add an alias to support the now deprecated formatted_* URL. # #Add an alias to support the now deprecated formatted_* URL.
|
187
|
+
def formatted_#{selector}(*args) # def formatted_users_url(*args)
|
188
|
+
ActiveSupport::Deprecation.warn( # ActiveSupport::Deprecation.warn(
|
189
|
+
"formatted_#{selector}() has been deprecated. " + # "formatted_users_url() has been deprecated. " +
|
190
|
+
"Please pass format to the standard " + # "Please pass format to the standard " +
|
191
|
+
"#{selector} method instead.", caller) # "users_url method instead.", caller)
|
192
|
+
#{selector}(*args) # users_url(*args)
|
193
|
+
end # end
|
194
|
+
protected :#{selector} # protected :users_url
|
195
|
+
end_eval
|
196
|
+
helpers << selector
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
attr_accessor :routes, :named_routes, :configuration_files
|
201
|
+
|
202
|
+
def initialize
|
203
|
+
self.configuration_files = []
|
204
|
+
|
205
|
+
self.routes = []
|
206
|
+
self.named_routes = NamedRouteCollection.new
|
207
|
+
|
208
|
+
clear!
|
209
|
+
end
|
210
|
+
|
211
|
+
def draw(&block)
|
212
|
+
clear!
|
213
|
+
Mapper.new(self).instance_exec(DeprecatedMapper.new(self), &block)
|
214
|
+
@set.add_route(NotFound)
|
215
|
+
install_helpers
|
216
|
+
@set.freeze
|
217
|
+
end
|
218
|
+
|
219
|
+
def clear!
|
220
|
+
routes.clear
|
221
|
+
named_routes.clear
|
222
|
+
@set = ::Rack::Mount::RouteSet.new(:parameters_key => PARAMETERS_KEY)
|
223
|
+
end
|
224
|
+
|
225
|
+
def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
|
226
|
+
Array(destinations).each { |d| d.module_eval { include Helpers } }
|
227
|
+
named_routes.install(destinations, regenerate_code)
|
228
|
+
end
|
229
|
+
|
230
|
+
def empty?
|
231
|
+
routes.empty?
|
232
|
+
end
|
233
|
+
|
234
|
+
def add_configuration_file(path)
|
235
|
+
self.configuration_files << path
|
236
|
+
end
|
237
|
+
|
238
|
+
# Deprecated accessor
|
239
|
+
def configuration_file=(path)
|
240
|
+
add_configuration_file(path)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Deprecated accessor
|
244
|
+
def configuration_file
|
245
|
+
configuration_files
|
246
|
+
end
|
247
|
+
|
248
|
+
def load!
|
249
|
+
Routing.use_controllers!(nil) # Clear the controller cache so we may discover new ones
|
250
|
+
load_routes!
|
251
|
+
end
|
252
|
+
|
253
|
+
# reload! will always force a reload whereas load checks the timestamp first
|
254
|
+
alias reload! load!
|
255
|
+
|
256
|
+
def reload
|
257
|
+
if configuration_files.any? && @routes_last_modified
|
258
|
+
if routes_changed_at == @routes_last_modified
|
259
|
+
return # routes didn't change, don't reload
|
260
|
+
else
|
261
|
+
@routes_last_modified = routes_changed_at
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
load!
|
266
|
+
end
|
267
|
+
|
268
|
+
def load_routes!
|
269
|
+
if configuration_files.any?
|
270
|
+
configuration_files.each { |config| load(config) }
|
271
|
+
@routes_last_modified = routes_changed_at
|
272
|
+
else
|
273
|
+
draw do |map|
|
274
|
+
map.connect ":controller/:action/:id"
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def routes_changed_at
|
280
|
+
routes_changed_at = nil
|
281
|
+
|
282
|
+
configuration_files.each do |config|
|
283
|
+
config_changed_at = File.stat(config).mtime
|
284
|
+
|
285
|
+
if routes_changed_at.nil? || config_changed_at > routes_changed_at
|
286
|
+
routes_changed_at = config_changed_at
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
routes_changed_at
|
291
|
+
end
|
292
|
+
|
293
|
+
def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil)
|
294
|
+
route = Route.new(app, conditions, requirements, defaults, name)
|
295
|
+
@set.add_route(*route)
|
296
|
+
named_routes[name] = route if name
|
297
|
+
routes << route
|
298
|
+
route
|
299
|
+
end
|
300
|
+
|
301
|
+
def options_as_params(options)
|
302
|
+
# If an explicit :controller was given, always make :action explicit
|
303
|
+
# too, so that action expiry works as expected for things like
|
304
|
+
#
|
305
|
+
# generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
|
306
|
+
#
|
307
|
+
# (the above is from the unit tests). In the above case, because the
|
308
|
+
# controller was explicitly given, but no action, the action is implied to
|
309
|
+
# be "index", not the recalled action of "show".
|
310
|
+
#
|
311
|
+
# great fun, eh?
|
312
|
+
|
313
|
+
options_as_params = options.clone
|
314
|
+
options_as_params[:action] ||= 'index' if options[:controller]
|
315
|
+
options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action]
|
316
|
+
options_as_params
|
317
|
+
end
|
318
|
+
|
319
|
+
def build_expiry(options, recall)
|
320
|
+
recall.inject({}) do |expiry, (key, recalled_value)|
|
321
|
+
expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param)
|
322
|
+
expiry
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# Generate the path indicated by the arguments, and return an array of
|
327
|
+
# the keys that were not used to generate it.
|
328
|
+
def extra_keys(options, recall={})
|
329
|
+
generate_extras(options, recall).last
|
330
|
+
end
|
331
|
+
|
332
|
+
def generate_extras(options, recall={})
|
333
|
+
generate(options, recall, :generate_extras)
|
334
|
+
end
|
335
|
+
|
336
|
+
def generate(options, recall = {}, method = :generate)
|
337
|
+
options, recall = options.dup, recall.dup
|
338
|
+
named_route = options.delete(:use_route)
|
339
|
+
|
340
|
+
options = options_as_params(options)
|
341
|
+
expire_on = build_expiry(options, recall)
|
342
|
+
|
343
|
+
recall[:action] ||= 'index' if options[:controller] || recall[:controller]
|
344
|
+
|
345
|
+
if recall[:controller] && (!options.has_key?(:controller) || options[:controller] == recall[:controller])
|
346
|
+
options[:controller] = recall.delete(:controller)
|
347
|
+
|
348
|
+
if recall[:action] && (!options.has_key?(:action) || options[:action] == recall[:action])
|
349
|
+
options[:action] = recall.delete(:action)
|
350
|
+
|
351
|
+
if recall[:id] && (!options.has_key?(:id) || options[:id] == recall[:id])
|
352
|
+
options[:id] = recall.delete(:id)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
options[:controller] = options[:controller].to_s if options[:controller]
|
358
|
+
|
359
|
+
if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/
|
360
|
+
old_parts = recall[:controller].split('/')
|
361
|
+
new_parts = options[:controller].split('/')
|
362
|
+
parts = old_parts[0..-(new_parts.length + 1)] + new_parts
|
363
|
+
options[:controller] = parts.join('/')
|
364
|
+
end
|
365
|
+
|
366
|
+
options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/
|
367
|
+
|
368
|
+
merged = options.merge(recall)
|
369
|
+
if options.has_key?(:action) && options[:action].nil?
|
370
|
+
options.delete(:action)
|
371
|
+
recall[:action] = 'index'
|
372
|
+
end
|
373
|
+
recall[:action] = options.delete(:action) if options[:action] == 'index'
|
374
|
+
|
375
|
+
path = _uri(named_route, options, recall)
|
376
|
+
if path && method == :generate_extras
|
377
|
+
uri = URI(path)
|
378
|
+
extras = uri.query ?
|
379
|
+
Rack::Utils.parse_nested_query(uri.query).keys.map { |k| k.to_sym } :
|
380
|
+
[]
|
381
|
+
[uri.path, extras]
|
382
|
+
elsif path
|
383
|
+
path
|
384
|
+
else
|
385
|
+
raise ActionController::RoutingError, "No route matches #{options.inspect}"
|
386
|
+
end
|
387
|
+
rescue Rack::Mount::RoutingError
|
388
|
+
raise ActionController::RoutingError, "No route matches #{options.inspect}"
|
389
|
+
end
|
390
|
+
|
391
|
+
def call(env)
|
392
|
+
@set.call(env)
|
393
|
+
rescue ActionController::RoutingError => e
|
394
|
+
raise e if env['action_controller.rescue_error'] == false
|
395
|
+
|
396
|
+
method, path = env['REQUEST_METHOD'].downcase.to_sym, env['PATH_INFO']
|
397
|
+
|
398
|
+
# Route was not recognized. Try to find out why (maybe wrong verb).
|
399
|
+
allows = HTTP_METHODS.select { |verb|
|
400
|
+
begin
|
401
|
+
recognize_path(path, {:method => verb}, false)
|
402
|
+
rescue ActionController::RoutingError
|
403
|
+
nil
|
404
|
+
end
|
405
|
+
}
|
406
|
+
|
407
|
+
if !HTTP_METHODS.include?(method)
|
408
|
+
raise ActionController::NotImplemented.new(*allows)
|
409
|
+
elsif !allows.empty?
|
410
|
+
raise ActionController::MethodNotAllowed.new(*allows)
|
411
|
+
else
|
412
|
+
raise e
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
def recognize(request)
|
417
|
+
params = recognize_path(request.path, extract_request_environment(request))
|
418
|
+
request.path_parameters = params.with_indifferent_access
|
419
|
+
"#{params[:controller].to_s.camelize}Controller".constantize
|
420
|
+
end
|
421
|
+
|
422
|
+
def recognize_path(path, environment = {}, rescue_error = true)
|
423
|
+
method = (environment[:method] || "GET").to_s.upcase
|
424
|
+
|
425
|
+
begin
|
426
|
+
env = Rack::MockRequest.env_for(path, {:method => method})
|
427
|
+
rescue URI::InvalidURIError => e
|
428
|
+
raise ActionController::RoutingError, e.message
|
429
|
+
end
|
430
|
+
|
431
|
+
env['action_controller.recognize'] = true
|
432
|
+
env['action_controller.rescue_error'] = rescue_error
|
433
|
+
status, headers, body = call(env)
|
434
|
+
body
|
435
|
+
end
|
436
|
+
|
437
|
+
# Subclasses and plugins may override this method to extract further attributes
|
438
|
+
# from the request, for use by route conditions and such.
|
439
|
+
def extract_request_environment(request)
|
440
|
+
{ :method => request.method }
|
441
|
+
end
|
442
|
+
|
443
|
+
private
|
444
|
+
def _uri(named_route, params, recall)
|
445
|
+
params = URISegment.wrap_values(params)
|
446
|
+
recall = URISegment.wrap_values(recall)
|
447
|
+
|
448
|
+
unless result = @set.generate(:path_info, named_route, params, recall)
|
449
|
+
return
|
450
|
+
end
|
451
|
+
|
452
|
+
uri, params = result
|
453
|
+
params.each do |k, v|
|
454
|
+
if v._value
|
455
|
+
params[k] = v._value
|
456
|
+
else
|
457
|
+
params.delete(k)
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
uri << "?#{Rack::Mount::Utils.build_nested_query(params)}" if uri && params.any?
|
462
|
+
uri
|
463
|
+
end
|
464
|
+
|
465
|
+
class URISegment < Struct.new(:_value, :_escape)
|
466
|
+
EXCLUDED = [:controller]
|
467
|
+
|
468
|
+
def self.wrap_values(hash)
|
469
|
+
hash.inject({}) { |h, (k, v)|
|
470
|
+
h[k] = new(v, !EXCLUDED.include?(k.to_sym))
|
471
|
+
h
|
472
|
+
}
|
473
|
+
end
|
474
|
+
|
475
|
+
extend Forwardable
|
476
|
+
def_delegators :_value, :==, :eql?, :hash
|
477
|
+
|
478
|
+
def to_param
|
479
|
+
@to_param ||= begin
|
480
|
+
if _value.is_a?(Array)
|
481
|
+
_value.map { |v| _escaped(v) }.join('/')
|
482
|
+
else
|
483
|
+
_escaped(_value)
|
484
|
+
end
|
485
|
+
end
|
486
|
+
end
|
487
|
+
alias_method :to_s, :to_param
|
488
|
+
|
489
|
+
private
|
490
|
+
def _escaped(value)
|
491
|
+
v = value.respond_to?(:to_param) ? value.to_param : value
|
492
|
+
_escape ? Rack::Mount::Utils.escape_uri(v) : v.to_s
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end
|
496
|
+
end
|
497
|
+
end
|