root/trunk/actionpack/lib/action_controller/routing.rb
| Revision 9226, 11.8 kB (checked in by pratik, 4 months ago) |
|---|
| 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.