halorgium-actionpack 3.0.pre

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