OSDN Git Service

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