Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/trunk/actionpack/lib/action_controller/routing.rb

Revision 9226, 11.8 kB (checked in by pratik, 2 years ago)

Improve documentation.

Line 
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 routes.rb in your RAILS_ROOT/config directory.
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   # :controller followed by an :action that in turn is fed some :id.
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 routes.rb 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   #  ActionController::Routing:Routes.draw do |map|
79   #    map.connect ':controller/:action/:id', :controller => 'blog'
80   #  end
81   #
82   # This sets up +blog+ as the default controller if no other is specified.
83   # This means visiting '/' would invoke the blog controller.
84   #
85   # More formally, you can define defaults in a route with the <tt>:defaults</tt> key.
86   #
87   #   map.connect ':controller/:action/:id', :action => 'show', :defaults => { :page => 'Dashboard' }
88   #
89   # == Named routes
90   #
91   # Routes can be named with the syntax <tt>map.name_of_route options</tt>,
92   # allowing for easy reference within your source as +name_of_route_url+
93   # for the full URL and +name_of_route_path+ for the URI path.
94   #
95   # Example:
96   #   # In routes.rb
97   #   map.login 'login', :controller => 'accounts', :action => 'login'
98   #
99   #   # With render, redirect_to, tests, etc.
100   #   redirect_to login_url
101   #
102   # Arguments can be passed as well.
103   #
104   #   redirect_to show_item_path(:id => 25)
105   #
106   # Use <tt>map.root</tt> as a shorthand to name a route for the root path "".
107   #
108   #   # In routes.rb
109   #   map.root :controller => 'blogs'
110   #
111   #   # would recognize http://www.example.com/ as
112   #   params = { :controller => 'blogs', :action => 'index' }
113   #
114   #   # and provide these named routes
115   #   root_url   # => 'http://www.example.com/'
116   #   root_path  # => ''
117   #
118   # You can also specify an already-defined named route in your map.root call:
119   #
120   #   # In routes.rb
121   #   map.new_session :controller => 'sessions', :action => 'new'
122   #   map.root :new_session
123   #
124   # Note: when using +with_options+, the route is simply named after the
125   # method you call on the block parameter rather than map.
126   #
127   #   # In routes.rb
128   #   map.with_options :controller => 'blog' do |blog|
129   #     blog.show    '',            :action  => 'list'
130   #     blog.delete  'delete/:id',  :action  => 'delete',
131   #     blog.edit    'edit/:id',    :action  => 'edit'
132   #   end
133   #
134   #   # provides named routes for show, delete, and edit
135   #   link_to @article.title, show_path(:id => @article.id)
136   #
137   # == Pretty URLs
138   #
139   # Routes can generate pretty URLs. For example:
140   #
141   #  map.connect 'articles/:year/:month/:day',
142   #              :controller => 'articles',
143   #              :action     => 'find_by_date',
144   #              :year       => /\d{4}/,
145   #              :month      => /\d{1,2}/,
146   #              :day        => /\d{1,2}/
147   #
148   #  # Using the route above, the url below maps to:
149   #  # params = {:year => '2005', :month => '11', :day => '06'}
150   #  # http://localhost:3000/articles/2005/11/06
151   #
152   # == Regular Expressions and parameters
153   # You can specify a regular expression to define a format for a parameter.
154   #
155   #  map.geocode 'geocode/:postalcode', :controller => 'geocode',
156   #              :action => 'show', :postalcode => /\d{5}(-\d{4})?/
157   #
158   # or, more formally:
159   #
160   #   map.geocode 'geocode/:postalcode', :controller => 'geocode',
161   #               :action => 'show', :requirements => { :postalcode => /\d{5}(-\d{4})?/ }
162   #
163   # Formats can include the 'ignorecase' and 'extended syntax' regular
164   # expression modifiers:
165   #
166   #   map.geocode 'geocode/:postalcode', :controller => 'geocode',
167   #               :action => 'show', :postalcode => /hx\d\d\s\d[a-z]{2}/i
168   #
169   #   map.geocode 'geocode/:postalcode', :controller => 'geocode',
170   #               :action => 'show',:requirements => {
171   #                 :postalcode => /# Postcode format
172   #                                 \d{5} #Prefix
173   #                                 (-\d{4})? #Suffix
174   #                                 /x
175   #               }
176   #
177   # Using the multiline match modifier will raise an ArgumentError.
178   # Encoding regular expression modifiers are silently ignored. The
179   # match will always use the default encoding or ASCII.
180   #
181   # == Route globbing
182   #
183   # Specifying <tt>*[string]</tt> as part of a rule like:
184   #
185   #  map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
186   #
187   # will glob all remaining parts of the route that were not recognized earlier. This idiom
188   # must appear at the end of the path. The globbed values are in <tt>params[:path]</tt> in
189   # this case.
190   #
191   # == Route conditions
192   #
193   # With conditions you can define restrictions on routes. Currently the only valid condition is <tt>:method</tt>.
194   #
195   # * <tt>:method</tt> - Allows you to specify which method can access the route. Possible values are <tt>:post</tt>,
196   #   <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. The default value is <tt>:any</tt>,
197   #   <tt>:any</tt> means that any method can access the route.
198   #
199   # Example:
200   #
201   #   map.connect 'post/:id', :controller => 'posts', :action => 'show',
202   #               :conditions => { :method => :get }
203   #   map.connect 'post/:id', :controller => 'posts', :action => 'create_comment',
204   #               :conditions => { :method => :post }
205   #
206   # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
207   # URL will route to the <tt>show</tt> action.
208   #
209   # == Reloading routes
210   #
211   # You can reload routes if you feel you must:
212   #
213   #  ActionController::Routing::Routes.reload
214   #
215   # This will clear all named routes and reload routes.rb if the file has been modified from
216   # last load. To absolutely force reloading, use +reload!+.
217   #
218   # == Testing Routes
219   #
220   # The two main methods for testing your routes:
221   #
222   # === +assert_routing+
223   #
224   #  def test_movie_route_properly_splits
225   #   opts = {:controller => "plugin", :action => "checkout", :id => "2"}
226   #   assert_routing "plugin/checkout/2", opts
227   #  end
228   #
229   # +assert_routing+ lets you test whether or not the route properly resolves into options.
230   #
231   # === +assert_recognizes+
232   #
233   #  def test_route_has_options
234   #   opts = {:controller => "plugin", :action => "show", :id => "12"}
235   #   assert_recognizes opts, "/plugins/show/12"
236   #  end
237   #
238   # Note the subtle difference between the two: +assert_routing+ tests that
239   # a URL fits options while +assert_recognizes+ tests that a URL
240   # breaks into parameters properly.
241   #
242   # In tests you can simply pass the URL or named route to +get+ or +post+.
243   #
244   #  def send_to_jail
245   #    get '/jail'
246   #    assert_response :success
247   #    assert_template "jail/front"
248   #  end
249   #
250   #  def goes_to_login
251   #    get login_url
252   #    #...
253   #  end
254   #
255   # == View a list of all your routes
256   #
257   # Run <tt>rake routes</tt>.
258   #
259   module Routing
260     SEPARATORS = %w( / . ? )
261
262     HTTP_METHODS = [:get, :head, :post, :put, :delete]
263
264     ALLOWED_REQUIREMENTS_FOR_OPTIMISATION = [:controller, :action].to_set
265
266     # The root paths which may contain controller files
267     mattr_accessor :controller_paths
268     self.controller_paths = []
269
270     # A helper module to hold URL related helpers.
271     module Helpers
272       include PolymorphicRoutes
273     end
274
275     class << self
276       def with_controllers(names)
277         prior_controllers = @possible_controllers
278         use_controllers! names
279         yield
280       ensure
281         use_controllers! prior_controllers
282       end
283
284       def normalize_paths(paths)
285         # do the hokey-pokey of path normalization...
286         paths = paths.collect do |path|
287           path = path.
288             gsub("//", "/").           # replace double / chars with a single
289             gsub("\\\\", "\\").        # replace double \ chars with a single
290             gsub(%r{(.)[\\/]$}, '\1')  # drop final / or \ if path ends with it
291
292           # eliminate .. paths where possible
293           re = %r{[^/\\]+[/\\]\.\.[/\\]}
294           path.gsub!(re, "") while path.match(re)
295           path
296         end
297
298         # start with longest path, first
299         paths = paths.uniq.sort_by { |path| - path.length }
300       end
301
302       def possible_controllers
303         unless @possible_controllers
304           @possible_controllers = []
305
306           paths = controller_paths.select { |path| File.directory?(path) && path != "." }
307
308           seen_paths = Hash.new {|h, k| h[k] = true; false}
309           normalize_paths(paths).each do |load_path|
310             Dir["#{load_path}/**/*_controller.rb"].collect do |path|
311               next if seen_paths[path.gsub(%r{^\.[/\\]}, "")]
312
313               controller_name = path[(load_path.length + 1)..-1]
314
315               controller_name.gsub!(/_controller\.rb\Z/, '')
316               @possible_controllers << controller_name
317             end
318           end
319
320           # remove duplicates
321           @possible_controllers.uniq!
322         end
323         @possible_controllers
324       end
325
326       def use_controllers!(controller_names)
327         @possible_controllers = controller_names
328       end
329
330       def controller_relative_to(controller, previous)
331         if controller.nil?           then previous
332         elsif controller[0] == ?/    then controller[1..-1]
333         elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}"
334         else controller
335         end
336       end
337     end
338    
339
340     Routes = RouteSet.new
341
342     ::Inflector.module_eval do
343       def inflections_with_route_reloading(&block)
344         returning(inflections_without_route_reloading(&block)) {
345           ActionController::Routing::Routes.reload! if block_given?
346         }
347       end
348
349       alias_method_chain :inflections, :route_reloading
350     end
351   end
352 end
Note: See TracBrowser for help on using the browser.