8d51e823a6091c1b380dda91dabd4dcb8bf0a69d
[depot.git] / routing.rb
1 require 'cgi'
2 require 'uri'
3 require 'action_controller/polymorphic_routes'
4 require 'action_controller/routing/optimisations'
5 require 'action_controller/routing/routing_ext'
6 require 'action_controller/routing/route'
7 require 'action_controller/routing/segments'
8 require 'action_controller/routing/builder'
9 require 'action_controller/routing/route_set'
10 require 'action_controller/routing/recognition_optimisation'
11
12 module ActionController
13 # == Routing
14 #
15 # The routing module provides URL rewriting in native Ruby. It's a way to
16 # redirect incoming requests to controllers and actions. This replaces
17 # mod_rewrite rules. Best of all, Rails' Routing works with any web server.
18 # Routes are defined in <tt>config/routes.rb</tt>.
19 #
20 # Consider the following route, installed by Rails when you generate your
21 # application:
22 #
23 # map.connect ':controller/:action/:id'
24 #
25 # This route states that it expects requests to consist of a
26 # <tt>:controller</tt> followed by an <tt>:action</tt> that in turn is fed
27 # some <tt>:id</tt>.
28 #
29 # Suppose you get an incoming request for <tt>/blog/edit/22</tt>, you'll end up
30 # with:
31 #
32 # params = { :controller => 'blog',
33 # :action => 'edit',
34 # :id => '22'
35 # }
36 #
37 # Think of creating routes as drawing a map for your requests. The map tells
38 # them where to go based on some predefined pattern:
39 #
40 # ActionController::Routing::Routes.draw do |map|
41 # Pattern 1 tells some request to go to one place
42 # Pattern 2 tell them to go to another
43 # ...
44 # end
45 #
46 # The following symbols are special:
47 #
48 # :controller maps to your controller name
49 # :action maps to an action with your controllers
50 #
51 # Other names simply map to a parameter as in the case of <tt>:id</tt>.
52 #
53 # == Route priority
54 #
55 # Not all routes are created equally. Routes have priority defined by the
56 # order of appearance of the routes in the <tt>config/routes.rb</tt> file. The priority goes
57 # from top to bottom. The last route in that file is at the lowest priority
58 # and will be applied last. If no route matches, 404 is returned.
59 #
60 # Within blocks, the empty pattern is at the highest priority.
61 # In practice this works out nicely:
62 #
63 # ActionController::Routing::Routes.draw do |map|
64 # map.with_options :controller => 'blog' do |blog|
65 # blog.show '', :action => 'list'
66 # end
67 # map.connect ':controller/:action/:view'
68 # end
69 #
70 # In this case, invoking blog controller (with an URL like '/blog/')
71 # without parameters will activate the 'list' action by default.
72 #
73 # == Defaults routes and default parameters
74 #
75 # Setting a default route is straightforward in Rails - you simply append a
76 # Hash at the end of your mapping to set any default parameters.
77 #
78 # Example:
79 #
80 # ActionController::Routing:Routes.draw do |map|
81 # map.connect ':controller/:action/:id', :controller => 'blog'
82 # end
83 #
84 # This sets up +blog+ as the default controller if no other is specified.
85 # This means visiting '/' would invoke the blog controller.
86 #
87 # More formally, you can define defaults in a route with the <tt>:defaults</tt> key.
88 #
89 # map.connect ':controller/:action/:id', :action => 'show', :defaults => { :page => 'Dashboard' }
90 #
91 # Note: The default routes, as provided by the Rails generator, make all actions in every
92 # controller accessible via GET requests. You should consider removing them or commenting
93 # them out if you're using named routes and resources.
94 #
95 # == Named routes
96 #
97 # Routes can be named with the syntax <tt>map.name_of_route options</tt>,
98 # allowing for easy reference within your source as +name_of_route_url+
99 # for the full URL and +name_of_route_path+ for the URI path.
100 #
101 # Example:
102 #
103 # # In routes.rb
104 # map.login 'login', :controller => 'accounts', :action => 'login'
105 #
106 # # With render, redirect_to, tests, etc.
107 # redirect_to login_url
108 #
109 # Arguments can be passed as well.
110 #
111 # redirect_to show_item_path(:id => 25)
112 #
113 # Use <tt>map.root</tt> as a shorthand to name a route for the root path "".
114 #
115 # # In routes.rb
116 # map.root :controller => 'blogs'
117 #
118 # # would recognize http://www.example.com/ as
119 # params = { :controller => 'blogs', :action => 'index' }
120 #
121 # # and provide these named routes
122 # root_url # => 'http://www.example.com/'
123 # root_path # => ''
124 #
125 # You can also specify an already-defined named route in your <tt>map.root</tt> call:
126 #
127 # # In routes.rb
128 # map.new_session :controller => 'sessions', :action => 'new'
129 # map.root :new_session
130 #
131 # Note: when using +with_options+, the route is simply named after the
132 # method you call on the block parameter rather than map.
133 #
134 # # In routes.rb
135 # map.with_options :controller => 'blog' do |blog|
136 # blog.show '', :action => 'list'
137 # blog.delete 'delete/:id', :action => 'delete',
138 # blog.edit 'edit/:id', :action => 'edit'
139 # end
140 #
141 # # provides named routes for show, delete, and edit
142 # link_to @article.title, show_path(:id => @article.id)
143 #
144 # == Pretty URLs
145 #
146 # Routes can generate pretty URLs. For example:
147 #
148 # map.connect 'articles/:year/:month/:day',
149 # :controller => 'articles',
150 # :action => 'find_by_date',
151 # :year => /\d{4}/,
152 # :month => /\d{1,2}/,
153 # :day => /\d{1,2}/
154 #
155 # Using the route above, the URL "http://localhost:3000/articles/2005/11/06"
156 # maps to
157 #
158 # params = {:year => '2005', :month => '11', :day => '06'}
159 #
160 # == Regular Expressions and parameters
161 # You can specify a regular expression to define a format for a parameter.
162 #
163 # map.geocode 'geocode/:postalcode', :controller => 'geocode',
164 # :action => 'show', :postalcode => /\d{5}(-\d{4})?/
165 #
166 # or, more formally:
167 #
168 # map.geocode 'geocode/:postalcode', :controller => 'geocode',
169 # :action => 'show', :requirements => { :postalcode => /\d{5}(-\d{4})?/ }
170 #
171 # Formats can include the 'ignorecase' and 'extended syntax' regular
172 # expression modifiers:
173 #
174 # map.geocode 'geocode/:postalcode', :controller => 'geocode',
175 # :action => 'show', :postalcode => /hx\d\d\s\d[a-z]{2}/i
176 #
177 # map.geocode 'geocode/:postalcode', :controller => 'geocode',
178 # :action => 'show',:requirements => {
179 # :postalcode => /# Postcode format
180 # \d{5} #Prefix
181 # (-\d{4})? #Suffix
182 # /x
183 # }
184 #
185 # Using the multiline match modifier will raise an ArgumentError.
186 # Encoding regular expression modifiers are silently ignored. The
187 # match will always use the default encoding or ASCII.
188 #
189 # == Route globbing
190 #
191 # Specifying <tt>*[string]</tt> as part of a rule like:
192 #
193 # map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
194 #
195 # will glob all remaining parts of the route that were not recognized earlier. This idiom
196 # must appear at the end of the path. The globbed values are in <tt>params[:path]</tt> in
197 # this case.
198 #
199 # == Route conditions
200 #
201 # With conditions you can define restrictions on routes. Currently the only valid condition is <tt>:method</tt>.
202 #
203 # * <tt>:method</tt> - Allows you to specify which method can access the route. Possible values are <tt>:post</tt>,
204 # <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. The default value is <tt>:any</tt>,
205 # <tt>:any</tt> means that any method can access the route.
206 #
207 # Example:
208 #
209 # map.connect 'post/:id', :controller => 'posts', :action => 'show',
210 # :conditions => { :method => :get }
211 # map.connect 'post/:id', :controller => 'posts', :action => 'create_comment',
212 # :conditions => { :method => :post }
213 #
214 # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
215 # URL will route to the <tt>show</tt> action.
216 #
217 # == Reloading routes
218 #
219 # You can reload routes if you feel you must:
220 #
221 # ActionController::Routing::Routes.reload
222 #
223 # This will clear all named routes and reload routes.rb if the file has been modified from
224 # last load. To absolutely force reloading, use <tt>reload!</tt>.
225 #
226 # == Testing Routes
227 #
228 # The two main methods for testing your routes:
229 #
230 # === +assert_routing+
231 #
232 # def test_movie_route_properly_splits
233 # opts = {:controller => "plugin", :action => "checkout", :id => "2"}
234 # assert_routing "plugin/checkout/2", opts
235 # end
236 #
237 # +assert_routing+ lets you test whether or not the route properly resolves into options.
238 #
239 # === +assert_recognizes+
240 #
241 # def test_route_has_options
242 # opts = {:controller => "plugin", :action => "show", :id => "12"}
243 # assert_recognizes opts, "/plugins/show/12"
244 # end
245 #
246 # Note the subtle difference between the two: +assert_routing+ tests that
247 # a URL fits options while +assert_recognizes+ tests that a URL
248 # breaks into parameters properly.
249 #
250 # In tests you can simply pass the URL or named route to +get+ or +post+.
251 #
252 # def send_to_jail
253 # get '/jail'
254 # assert_response :success
255 # assert_template "jail/front"
256 # end
257 #
258 # def goes_to_login
259 # get login_url
260 # #...
261 # end
262 #
263 # == View a list of all your routes
264 #
265 # Run <tt>rake routes</tt>.
266 #
267 module Routing
268 SEPARATORS = %w( / . ? )
269
270 HTTP_METHODS = [:get, :head, :post, :put, :delete]
271
272 ALLOWED_REQUIREMENTS_FOR_OPTIMISATION = [:controller, :action].to_set
273
274 # The root paths which may contain controller files
275 mattr_accessor :controller_paths
276 self.controller_paths = []
277
278 # A helper module to hold URL related helpers.
279 module Helpers
280 include PolymorphicRoutes
281 end
282
283 class << self
284 # Expects an array of controller names as the first argument.
285 # Executes the passed block with only the named controllers named available.
286 # This method is used in internal Rails testing.
287 def with_controllers(names)
288 prior_controllers = @possible_controllers
289 use_controllers! names
290 yield
291 ensure
292 use_controllers! prior_controllers
293 end
294
295 # Returns an array of paths, cleaned of double-slashes and relative path references.
296 # * "\\\" and "//" become "\\" or "/".
297 # * "/foo/bar/../config" becomes "/foo/config".
298 # The returned array is sorted by length, descending.
299 def normalize_paths(paths)
300 # do the hokey-pokey of path normalization...
301 paths = paths.collect do |path|
302 path = path.
303 gsub("//", "/"). # replace double / chars with a single
304 gsub("\\\\", "\\"). # replace double \ chars with a single
305 gsub(%r{(.)[\\/]$}, '\1') # drop final / or \ if path ends with it
306
307 # eliminate .. paths where possible
308 re = %r{[^/\\]+[/\\]\.\.[/\\]}
309 path.gsub!(re, "") while path.match(re)
310 path
311 end
312
313 # start with longest path, first
314 paths = paths.uniq.sort_by { |path| - path.length }
315 end
316
317 # Returns the array of controller names currently available to ActionController::Routing.
318 def possible_controllers
319 unless @possible_controllers
320 @possible_controllers = []
321
322 paths = controller_paths.select { |path| File.directory?(path) && path != "." }
323
324 seen_paths = Hash.new {|h, k| h[k] = true; false}
325 normalize_paths(paths).each do |load_path|
326 Dir["#{load_path}/**/*_controller.rb"].collect do |path|
327 next if seen_paths[path.gsub(%r{^\.[/\\]}, "")]
328
329 controller_name = path[(load_path.length + 1)..-1]
330
331 controller_name.gsub!(/_controller\.rb\Z/, '')
332 @possible_controllers << controller_name
333 end
334 end
335
336 # remove duplicates
337 @possible_controllers.uniq!
338 end
339 @possible_controllers
340 end
341
342 # Replaces the internal list of controllers available to ActionController::Routing with the passed argument.
343 # ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ])
344 def use_controllers!(controller_names)
345 @possible_controllers = controller_names
346 end
347
348 # Returns a controller path for a new +controller+ based on a +previous+ controller path.
349 # Handles 4 scenarios:
350 #
351 # * stay in the previous controller:
352 # controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion"
353 #
354 # * stay in the previous namespace:
355 # controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts"
356 #
357 # * forced move to the root namespace:
358 # controller_relative_to( "/posts", "groups/discussion" ) # => "posts"
359 #
360 # * previous namespace is root:
361 # controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts"
362 #
363 def controller_relative_to(controller, previous)
364 if controller.nil? then previous
365 elsif controller[0] == ?/ then controller[1..-1]
366 elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}"
367 else controller
368 end
369 end
370 end
371
372 Routes = RouteSet.new
373
374 ActiveSupport::Inflector.module_eval do
375 # Ensures that routes are reloaded when Rails inflections are updated.
376 def inflections_with_route_reloading(&block)
377 returning(inflections_without_route_reloading(&block)) {
378 ActionController::Routing::Routes.reload! if block_given?
379 }
380 end
381
382 alias_method_chain :inflections, :route_reloading
383 end
384 end
385 end