halorgium-actionpack 3.0.pre

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 (154) hide show
  1. data/CHANGELOG +5179 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +409 -0
  4. data/lib/abstract_controller.rb +16 -0
  5. data/lib/abstract_controller/base.rb +158 -0
  6. data/lib/abstract_controller/callbacks.rb +113 -0
  7. data/lib/abstract_controller/exceptions.rb +12 -0
  8. data/lib/abstract_controller/helpers.rb +151 -0
  9. data/lib/abstract_controller/layouts.rb +250 -0
  10. data/lib/abstract_controller/localized_cache.rb +49 -0
  11. data/lib/abstract_controller/logger.rb +61 -0
  12. data/lib/abstract_controller/rendering_controller.rb +188 -0
  13. data/lib/action_controller.rb +72 -0
  14. data/lib/action_controller/base.rb +168 -0
  15. data/lib/action_controller/caching.rb +80 -0
  16. data/lib/action_controller/caching/actions.rb +163 -0
  17. data/lib/action_controller/caching/fragments.rb +116 -0
  18. data/lib/action_controller/caching/pages.rb +154 -0
  19. data/lib/action_controller/caching/sweeping.rb +97 -0
  20. data/lib/action_controller/deprecated.rb +4 -0
  21. data/lib/action_controller/deprecated/integration_test.rb +2 -0
  22. data/lib/action_controller/deprecated/performance_test.rb +1 -0
  23. data/lib/action_controller/dispatch/dispatcher.rb +57 -0
  24. data/lib/action_controller/metal.rb +129 -0
  25. data/lib/action_controller/metal/benchmarking.rb +73 -0
  26. data/lib/action_controller/metal/compatibility.rb +145 -0
  27. data/lib/action_controller/metal/conditional_get.rb +86 -0
  28. data/lib/action_controller/metal/configuration.rb +28 -0
  29. data/lib/action_controller/metal/cookies.rb +105 -0
  30. data/lib/action_controller/metal/exceptions.rb +55 -0
  31. data/lib/action_controller/metal/filter_parameter_logging.rb +77 -0
  32. data/lib/action_controller/metal/flash.rb +162 -0
  33. data/lib/action_controller/metal/head.rb +27 -0
  34. data/lib/action_controller/metal/helpers.rb +115 -0
  35. data/lib/action_controller/metal/hide_actions.rb +47 -0
  36. data/lib/action_controller/metal/http_authentication.rb +312 -0
  37. data/lib/action_controller/metal/layouts.rb +171 -0
  38. data/lib/action_controller/metal/mime_responds.rb +317 -0
  39. data/lib/action_controller/metal/rack_convenience.rb +27 -0
  40. data/lib/action_controller/metal/redirector.rb +22 -0
  41. data/lib/action_controller/metal/render_options.rb +103 -0
  42. data/lib/action_controller/metal/rendering_controller.rb +57 -0
  43. data/lib/action_controller/metal/request_forgery_protection.rb +108 -0
  44. data/lib/action_controller/metal/rescuable.rb +13 -0
  45. data/lib/action_controller/metal/responder.rb +200 -0
  46. data/lib/action_controller/metal/session.rb +15 -0
  47. data/lib/action_controller/metal/session_management.rb +45 -0
  48. data/lib/action_controller/metal/streaming.rb +188 -0
  49. data/lib/action_controller/metal/testing.rb +39 -0
  50. data/lib/action_controller/metal/url_for.rb +41 -0
  51. data/lib/action_controller/metal/verification.rb +130 -0
  52. data/lib/action_controller/middleware.rb +38 -0
  53. data/lib/action_controller/notifications.rb +10 -0
  54. data/lib/action_controller/polymorphic_routes.rb +183 -0
  55. data/lib/action_controller/record_identifier.rb +91 -0
  56. data/lib/action_controller/testing/process.rb +111 -0
  57. data/lib/action_controller/testing/test_case.rb +345 -0
  58. data/lib/action_controller/translation.rb +13 -0
  59. data/lib/action_controller/url_rewriter.rb +204 -0
  60. data/lib/action_controller/vendor/html-scanner.rb +16 -0
  61. data/lib/action_controller/vendor/html-scanner/html/document.rb +68 -0
  62. data/lib/action_controller/vendor/html-scanner/html/node.rb +537 -0
  63. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +176 -0
  64. data/lib/action_controller/vendor/html-scanner/html/selector.rb +828 -0
  65. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +105 -0
  66. data/lib/action_controller/vendor/html-scanner/html/version.rb +11 -0
  67. data/lib/action_dispatch.rb +70 -0
  68. data/lib/action_dispatch/http/headers.rb +33 -0
  69. data/lib/action_dispatch/http/mime_type.rb +231 -0
  70. data/lib/action_dispatch/http/mime_types.rb +23 -0
  71. data/lib/action_dispatch/http/request.rb +539 -0
  72. data/lib/action_dispatch/http/response.rb +290 -0
  73. data/lib/action_dispatch/http/status_codes.rb +42 -0
  74. data/lib/action_dispatch/http/utils.rb +20 -0
  75. data/lib/action_dispatch/middleware/callbacks.rb +50 -0
  76. data/lib/action_dispatch/middleware/params_parser.rb +79 -0
  77. data/lib/action_dispatch/middleware/rescue.rb +26 -0
  78. data/lib/action_dispatch/middleware/session/abstract_store.rb +208 -0
  79. data/lib/action_dispatch/middleware/session/cookie_store.rb +235 -0
  80. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +47 -0
  81. data/lib/action_dispatch/middleware/show_exceptions.rb +143 -0
  82. data/lib/action_dispatch/middleware/stack.rb +116 -0
  83. data/lib/action_dispatch/middleware/static.rb +44 -0
  84. data/lib/action_dispatch/middleware/string_coercion.rb +29 -0
  85. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +24 -0
  86. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +26 -0
  87. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +10 -0
  88. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +29 -0
  89. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +2 -0
  90. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +10 -0
  91. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +21 -0
  92. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +2 -0
  93. data/lib/action_dispatch/routing.rb +381 -0
  94. data/lib/action_dispatch/routing/deprecated_mapper.rb +878 -0
  95. data/lib/action_dispatch/routing/mapper.rb +327 -0
  96. data/lib/action_dispatch/routing/route.rb +49 -0
  97. data/lib/action_dispatch/routing/route_set.rb +497 -0
  98. data/lib/action_dispatch/testing/assertions.rb +8 -0
  99. data/lib/action_dispatch/testing/assertions/dom.rb +35 -0
  100. data/lib/action_dispatch/testing/assertions/model.rb +19 -0
  101. data/lib/action_dispatch/testing/assertions/response.rb +145 -0
  102. data/lib/action_dispatch/testing/assertions/routing.rb +144 -0
  103. data/lib/action_dispatch/testing/assertions/selector.rb +639 -0
  104. data/lib/action_dispatch/testing/assertions/tag.rb +123 -0
  105. data/lib/action_dispatch/testing/integration.rb +504 -0
  106. data/lib/action_dispatch/testing/performance_test.rb +15 -0
  107. data/lib/action_dispatch/testing/test_request.rb +83 -0
  108. data/lib/action_dispatch/testing/test_response.rb +131 -0
  109. data/lib/action_pack.rb +24 -0
  110. data/lib/action_pack/version.rb +9 -0
  111. data/lib/action_view.rb +58 -0
  112. data/lib/action_view/base.rb +308 -0
  113. data/lib/action_view/context.rb +44 -0
  114. data/lib/action_view/erb/util.rb +48 -0
  115. data/lib/action_view/helpers.rb +62 -0
  116. data/lib/action_view/helpers/active_model_helper.rb +306 -0
  117. data/lib/action_view/helpers/ajax_helper.rb +68 -0
  118. data/lib/action_view/helpers/asset_tag_helper.rb +830 -0
  119. data/lib/action_view/helpers/atom_feed_helper.rb +198 -0
  120. data/lib/action_view/helpers/cache_helper.rb +39 -0
  121. data/lib/action_view/helpers/capture_helper.rb +168 -0
  122. data/lib/action_view/helpers/date_helper.rb +988 -0
  123. data/lib/action_view/helpers/debug_helper.rb +38 -0
  124. data/lib/action_view/helpers/form_helper.rb +1102 -0
  125. data/lib/action_view/helpers/form_options_helper.rb +600 -0
  126. data/lib/action_view/helpers/form_tag_helper.rb +495 -0
  127. data/lib/action_view/helpers/javascript_helper.rb +208 -0
  128. data/lib/action_view/helpers/number_helper.rb +311 -0
  129. data/lib/action_view/helpers/prototype_helper.rb +1309 -0
  130. data/lib/action_view/helpers/raw_output_helper.rb +9 -0
  131. data/lib/action_view/helpers/record_identification_helper.rb +20 -0
  132. data/lib/action_view/helpers/record_tag_helper.rb +58 -0
  133. data/lib/action_view/helpers/sanitize_helper.rb +259 -0
  134. data/lib/action_view/helpers/scriptaculous_helper.rb +226 -0
  135. data/lib/action_view/helpers/tag_helper.rb +151 -0
  136. data/lib/action_view/helpers/text_helper.rb +594 -0
  137. data/lib/action_view/helpers/translation_helper.rb +39 -0
  138. data/lib/action_view/helpers/url_helper.rb +639 -0
  139. data/lib/action_view/locale/en.yml +117 -0
  140. data/lib/action_view/paths.rb +80 -0
  141. data/lib/action_view/render/partials.rb +342 -0
  142. data/lib/action_view/render/rendering.rb +134 -0
  143. data/lib/action_view/safe_buffer.rb +28 -0
  144. data/lib/action_view/template/error.rb +101 -0
  145. data/lib/action_view/template/handler.rb +36 -0
  146. data/lib/action_view/template/handlers.rb +52 -0
  147. data/lib/action_view/template/handlers/builder.rb +17 -0
  148. data/lib/action_view/template/handlers/erb.rb +53 -0
  149. data/lib/action_view/template/handlers/rjs.rb +18 -0
  150. data/lib/action_view/template/resolver.rb +165 -0
  151. data/lib/action_view/template/template.rb +131 -0
  152. data/lib/action_view/template/text.rb +38 -0
  153. data/lib/action_view/test_case.rb +163 -0
  154. 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