h2. Rails Routing from the Outside In This guide covers the user-facing features of Rails routing. By referring to this guide, you will be able to: * Understand the purpose of routing * Decipher the code in +routes.rb+ * Construct your own routes, using either the classic hash style or the now-preferred RESTful style * Identify how a route will map to a controller and action endprologue. h3. The Dual Purpose of Routing Rails routing is a two-way piece of machinery - rather as if you could turn trees into paper, and then turn paper back into trees. Specifically, it both connects incoming HTTP requests to the code in your application's controllers, and helps you generate URLs without having to hard-code them as strings. h4. Connecting URLs to Code When your Rails application receives an incoming HTTP request, say
GET /patients/17the routing engine within Rails is the piece of code that dispatches the request to the appropriate spot in your application. In this case, the application would most likely end up running the +show+ action within the +patients+ controller, displaying the details of the patient whose ID is 17. h4. Generating URLs from Code Routing also works in reverse. If your application contains this code: @patient = Patient.find(17)
DELETE /photos/17would be understood to refer to a photo resource with the ID of 17, and to indicate a desired action - deleting that resource. REST is a natural style for the architecture of web applications, and Rails makes it even more natural by using conventions to shield you from some of the RESTful complexities. h4. CRUD, Verbs, and Actions In Rails, a RESTful route provides a mapping between HTTP verbs, controller actions, and (implicitly) CRUD operations in a database. A single entry in the routing file, such as map.resources :photos creates seven different routes in your application: |_.HTTP verb|_.URL |_.controller|_.action |_.used for| |GET |/photos |Photos |index |display a list of all photos| |GET |/photos/new |Photos |new |return an HTML form for creating a new photo| |POST |/photos |Photos |create |create a new photo| |GET |/photos/1 |Photos |show |display a specific photo| |GET |/photos/1/edit |Photos |edit |return an HTML form for editing a photo| |PUT |/photos/1 |Photos |update |update a specific photo| |DELETE |/photos/1 |Photos |destroy |delete a specific photo| For the specific routes (those that reference just a single resource), the identifier for the resource will be available within the corresponding controller action as +params[:id]+. TIP: If you consistently use RESTful routes in your application, you should disable the default routes in +routes.rb+ so that Rails will enforce the mapping between HTTP verbs and routes. h4. URLs and Paths Creating a RESTful route will also make available a pile of helpers within your application: * +photos_url+ and +photos_path+ map to the path for the index and create actions * +new_photo_url+ and +new_photo_path+ map to the path for the new action * +edit_photo_url+ and +edit_photo_path+ map to the path for the edit action * +photo_url+ and +photo_path+ map to the path for the show, update, and destroy actions NOTE: Because routing makes use of the HTTP verb as well as the path in the request to dispatch requests, the seven routes generated by a RESTful routing entry only give rise to four pairs of helpers. In each case, the +_url+ helper generates a string containing the entire URL that the application will understand, while the +_path+ helper generates a string containing the relative path from the root of the application. For example: photos_url # => "http://www.example.com/photos" photos_path # => "/photos" h4. Defining Multiple Resources at the Same Time If you need to create routes for more than one RESTful resource, you can save a bit of typing by defining them all with a single call to +map.resources+: map.resources :photos, :books, :videos This has exactly the same effect as map.resources :photos map.resources :books map.resources :videos h4. Singular Resources You can also apply RESTful routing to singleton resources within your application. In this case, you use +map.resource+ instead of +map.resources+ and the route generation is slightly different. For example, a routing entry of map.resource :geocoder creates six different routes in your application: |_.HTTP verb|_.URL |_.controller|_.action |_.used for| |GET |/geocoder/new |Geocoders |new |return an HTML form for creating the new geocoder| |POST |/geocoder |Geocoders |create |create the new geocoder| |GET |/geocoder |Geocoders |show |display the one and only geocoder resource| |GET |/geocoder/edit |Geocoders |edit |return an HTML form for editing the geocoder| |PUT |/geocoder |Geocoders |update |update the one and only geocoder resource| |DELETE |/geocoder |Geocoders |destroy |delete the geocoder resource| NOTE: Even though the name of the resource is singular in +routes.rb+, the matching controller is still plural. A singular RESTful route generates an abbreviated set of helpers: * +new_geocoder_url+ and +new_geocoder_path+ map to the path for the new action * +edit_geocoder_url+ and +edit_geocoder_path+ map to the path for the edit action * +geocoder_url+ and +geocoder_path+ map to the path for the create, show, update, and destroy actions h4. Customizing Resources Although the conventions of RESTful routing are likely to be sufficient for many applications, there are a number of ways to customize the way that RESTful routes work. These options include: * +:controller+ * +:singular+ * +:requirements+ * +:conditions+ * +:as+ * +:path_names+ * +:path_prefix+ * +:name_prefix+ * +:only+ * +:except+ You can also add additional routes via the +:member+ and +:collection+ options, which are discussed later in this guide. h5. Using +:controller+ The +:controller+ option lets you use a controller name that is different from the public-facing resource name. For example, this routing entry: map.resources :photos, :controller => "images" will recognize incoming URLs containing +photo+ but route the requests to the Images controller: |_.HTTP verb|_.URL |_.controller|_.action |_.used for| |GET |/photos |Images |index |display a list of all images| |GET |/photos/new |Images |new |return an HTML form for creating a new image| |POST |/photos |Images |create |create a new image| |GET |/photos/1 |Images |show |display a specific image| |GET |/photos/1/edit |Images |edit |return an HTML form for editing a image| |PUT |/photos/1 |Images |update |update a specific image| |DELETE |/photos/1 |Images |destroy |delete a specific image| NOTE: The helpers will be generated with the name of the resource, not the name of the controller. So in this case, you'd still get +photos_path+, +new_photo_path+, and so on. h4. Controller Namespaces and Routing Rails allows you to group your controllers into namespaces by saving them in folders underneath +app/controllers+. The +:controller+ option provides a convenient way to use these routes. For example, you might have a resource whose controller is purely for admin users in the +admin+ folder: map.resources :adminphotos, :controller => "admin/photos" If you use controller namespaces, you need to be aware of a subtlety in the Rails routing code: it always tries to preserve as much of the namespace from the previous request as possible. For example, if you are on a view generated from the +adminphoto_path+ helper, and you follow a link generated with +<%= link_to "show", adminphoto(1) %>+ you will end up on the view generated by +admin/photos/show+, but you will also end up in the same place if you have +<%= link_to "show", {:controller => "photos", :action => "show"} %>+ because Rails will generate the show URL relative to the current URL. TIP: If you want to guarantee that a link goes to a top-level controller, use a preceding slash to anchor the controller name: +<%= link_to "show", {:controller => "/photos", :action => "show"} %>+ You can also specify a controller namespace with the +:namespace+ option instead of a path: map.resources :adminphotos, :namespace => "admin", :controller => "photos" This can be especially useful when combined with +with_options+ to map multiple namespaced routes together: map.with_options(:namespace => "admin") do |admin| admin.resources :photos, :videos end That would give you routing for +admin/photos+ and +admin/videos+ controllers. h5. Using +:singular+ If for some reason Rails isn't doing what you want in converting the plural resource name to a singular name in member routes, you can override its judgment with the +:singular+ option: map.resources :teeth, :singular => "tooth" TIP: Depending on the other code in your application, you may prefer to add additional rules to the +Inflector+ class instead. h5. Using +:requirements+ You can use the +:requirements+ option in a RESTful route to impose a format on the implied +:id+ parameter in the singular routes. For example: map.resources :photos, :requirements => {:id => /[A-Z][A-Z][0-9]+/} This declaration constrains the +:id+ parameter to match the supplied regular expression. So, in this case, +/photos/1+ would no longer be recognized by this route, but +/photos/RR27+ would. h5. Using +:conditions+ Conditions in Rails routing are currently used only to set the HTTP verb for individual routes. Although in theory you can set this for RESTful routes, in practice there is no good reason to do so. (You'll learn more about conditions in the discussion of classic routing later in this guide.) h5. Using +:as+ The +:as+ option lets you override the normal naming for the actual generated paths. For example: map.resources :photos, :as => "images" will recognize incoming URLs containing +image+ but route the requests to the Photos controller: |_.HTTP verb|_.URL |_.controller|_.action |_:used for| |GET |/images |Photos |index |display a list of all photos| |GET |/images/new |Photos |new |return an HTML form for creating a new photo| |POST |/images |Photos |create |create a new photo| |GET |/images/1 |Photos |show |display a specific photo| |GET |/images/1/edit |Photos |edit |return an HTML form for editing a photo| |PUT |/images/1 |Photos |update |update a specific photo| |DELETE |/images/1 |Photos |destroy |delete a specific photo| NOTE: The helpers will be generated with the name of the resource, not the path name. So in this case, you'd still get +photos_path+, +new_photo_path+, and so on. h5. Using +:path_names+ The +:path_names+ option lets you override the automatically-generated "new" and "edit" segments in URLs: map.resources :photos, :path_names => { :new => 'make', :edit => 'change' } This would cause the routing to recognize URLs such as
/photos/make /photos/1/changeNOTE: The actual action names aren't changed by this option; the two URLs shown would still route to the new and edit actions. TIP: If you find yourself wanting to change this option uniformly for all of your routes, you can set a default in your environment: config.action_controller.resources_path_names = { :new => 'make', :edit => 'change' } h5. Using +:path_prefix+ The +:path_prefix+ option lets you add additional parameters that will be prefixed to the recognized paths. For example, suppose each photo in your application belongs to a particular photographer. In that case, you might declare this route: map.resources :photos, :path_prefix => '/photographers/:photographer_id' Routes recognized by this entry would include:
/photographers/1/photos/2 /photographers/1/photosNOTE: In most cases, it's simpler to recognize URLs of this sort by creating nested resources, as discussed in the next section. NOTE: You can also use +:path_prefix+ with non-RESTful routes. h5. Using +:name_prefix+ You can use the :name_prefix option to avoid collisions between routes. This is most useful when you have two resources with the same name that use +:path_prefix+ to map differently. For example: map.resources :photos, :path_prefix => '/photographers/:photographer_id', :name_prefix => 'photographer_' map.resources :photos, :path_prefix => '/agencies/:agency_id', :name_prefix => 'agency_' This combination will give you route helpers such as +photographer_photos_path+ and +agency_edit_photo_path+ to use in your code. NOTE: You can also use +:name_prefix+ with non-RESTful routes. h5. Using +:only+ and +:except+ By default, Rails creates routes for all seven of the default actions (index, show, new, create, edit, update, and destroy) for every RESTful route in your application. You can use the +:only+ and +:except+ options to fine-tune this behavior. The +:only+ option specifies that only certain routes should be generated: map.resources :photos, :only => [:index, :show] With this declaration, a +GET+ request to +/photos+ would succeed, but a +POST+ request to +/photos+ (which would ordinarily be routed to the create action) will fail. The +:except+ option specifies a route or list of routes that should _not_ be generated: map.resources :photos, :except => :destroy In this case, all of the normal routes except the route for +destroy+ (a +DELETE+ request to +/photos/id+) will be generated. In addition to an action or a list of actions, you can also supply the special symbols +:all+ or +:none+ to the +:only+ and +:except+ options. TIP: If your application has many RESTful routes, using +:only+ and +:except+ to generate only the routes that you actually need can cut down on memory use and speed up the routing process. h4. Nested Resources It's common to have resources that are logically children of other resources. For example, suppose your application includes these models: class Magazine < ActiveRecord::Base has_many :ads end class Ad < ActiveRecord::Base belongs_to :magazine end Each ad is logically subservient to one magazine. Nested routes allow you to capture this relationship in your routing. In this case, you might include this route declaration: map.resources :magazines do |magazine| magazine.resources :ads end TIP: Further below you'll learn about a convenient shortcut for this construct:
/publishers/1/magazines/2/photos/3The corresponding route helper would be +publisher_magazine_photo_url+, requiring you to specify objects at all three levels. Indeed, this situation is confusing enough that a popular "article":http://weblog.jamisbuck.org/2007/2/5/nesting-resources by Jamis Buck proposes a rule of thumb for good Rails design: TIP: _Resources should never be nested more than 1 level deep._ h5. Shallow Nesting The +:shallow+ option provides an elegant solution to the difficulties of deeply-nested routes. If you specify this option at any level of routing, then paths for nested resources which reference a specific member (that is, those with an +:id+ parameter) will not use the parent path prefix or name prefix. To see what this means, consider this set of routes: map.resources :publishers, :shallow => true do |publisher| publisher.resources :magazines do |magazine| magazine.resources :photos end end This will enable recognition of (among others) these routes:
/publishers/1 ==> publisher_path(1) /publishers/1/magazines ==> publisher_magazines_path(1) /magazines/2 ==> magazine_path(2) /magazines/2/photos ==> magazines_photos_path(2) /photos/3 ==> photo_path(3)With shallow nesting, you need only supply enough information to uniquely identify the resource that you want to work with. If you like, you can combine shallow nesting with the +:has_one+ and +:has_many+ options: map.resources :publishers, :has_many => { :magazines => :photos }, :shallow => true h4. Route Generation from Arrays In addition to using the generated routing helpers, Rails can also generate RESTful routes from an array of parameters. For example, suppose you have a set of routes generated with these entries in routes.rb: map.resources :magazines do |magazine| magazine.resources :ads end Rails will generate helpers such as magazine_ad_path that you can use in building links: <%= link_to "Ad details", magazine_ad_path(@magazine, @ad) %> Another way to refer to the same route is with an array of objects: <%= link_to "Ad details", [@magazine, @ad] %> This format is especially useful when you might not know until runtime which of several types of object will be used in a particular link. h4. Namespaced Resources It's possible to do some quite complex things by combining +:path_prefix+ and +:name_prefix+. For example, you can use the combination of these two options to move administrative resources to their own folder in your application: map.resources :photos, :path_prefix => 'admin', :controller => 'admin/photos' map.resources :tags, :name_prefix => 'admin_photo_', :path_prefix => 'admin/photos/:photo_id', :controller => 'admin/photo_tags' map.resources :ratings, :name_prefix => 'admin_photo_', :path_prefix => 'admin/photos/:photo_id', :controller => 'admin/photo_ratings' The good news is that if you find yourself using this level of complexity, you can stop. Rails supports _namespaced resources_ to make placing resources in their own folder a snap. Here's the namespaced version of those same three routes: map.namespace(:admin) do |admin| admin.resources :photos, :has_many => { :tags, :ratings} end As you can see, the namespaced version is much more succinct than the one that spells everything out - but it still creates the same routes. For example, you'll get +admin_photos_url+ that expects to find an +Admin::PhotosController+ and that matches +admin/photos+, and +admin_photos_ratings_path+ that matches +/admin/photos/_photo_id_/ratings+, expecting to use +Admin::RatingsController+. Even though you're not specifying +path_prefix+ explicitly, the routing code will calculate the appropriate +path_prefix+ from the route nesting. h4. Adding More RESTful Actions You are not limited to the seven routes that RESTful routing creates by default. If you like, you may add additional member routes (those which apply to a single instance of the resource), additional new routes (those that apply to creating a new resource), or additional collection routes (those which apply to the collection of resources as a whole). h5. Adding Member Routes To add a member route, use the +:member+ option: map.resources :photos, :member => { :preview => :get } This will enable Rails to recognize URLs such as +/photos/1/preview+ using the GET HTTP verb, and route them to the preview action of the Photos controller. It will also create a +preview_photo+ route helper. Within the hash of member routes, each route name specifies the HTTP verb that it will recognize. You can use +:get+, +:put+, +:post+, +:delete+, or +:any+ here. You can also specify an array of methods, if you need more than one but you don't want to allow just anything: map.resources :photos, :member => { :prepare => [:get, :post] } h5. Adding Collection Routes To add a collection route, use the +:collection+ option: map.resources :photos, :collection => { :search => :get } This will enable Rails to recognize URLs such as +/photos/search+ using the GET HTTP verb, and route them to the search action of the Photos controller. It will also create a +search_photos+ route helper. Just as with member routes, you can specify an array of methods for a collection route: map.resources :photos, :collection => { :search => [:get, :post] } h5. Adding New Routes To add a new route (one that creates a new resource), use the +:new+ option: map.resources :photos, :new => { :upload => :post } This will enable Rails to recognize URLs such as +/photos/upload+ using the POST HTTP verb, and route them to the upload action of the Photos controller. It will also create a +upload_photos+ route helper. TIP: If you want to redefine the verbs accepted by one of the standard actions, you can do so by explicitly mapping that action. For example:
users GET /users {:controller=>"users", :action=>"index"} formatted_users GET /users.:format {:controller=>"users", :action=>"index"} POST /users {:controller=>"users", :action=>"create"} POST /users.:format {:controller=>"users", :action=>"create"}TIP: You'll find that the output from +rake routes+ is much more readable if you widen your terminal window until the output lines don't wrap. h4. Testing Routes Routes should be included in your testing strategy (just like the rest of your application). Rails offers three "built-in assertions":http://api.rubyonrails.org/classes/ActionController/Assertions/RoutingAssertions.html designed to make testing routes simpler: * +assert_generates+ * +assert_recognizes+ * +assert_routing+ h5. The +assert_generates+ Assertion Use +assert_generates+ to assert that a particular set of options generate a particular path. You can use this with default routes or custom routes assert_generates "/photos/1", { :controller => "photos", :action => "show", :id => "1" } assert_generates "/about", :controller => "pages", :action => "about" h5. The +assert_recognizes+ Assertion The +assert_recognizes+ assertion is the inverse of +assert_generates+. It asserts that Rails recognizes the given path and routes it to a particular spot in your application. assert_recognizes { :controller => "photos", :action => "show", :id => "1" }, "/photos/1" You can supply a +:method+ argument to specify the HTTP verb: assert_recognizes { :controller => "photos", :action => "create" }, { :path => "photos", :method => :post } You can also use the RESTful helpers to test recognition of a RESTful route: assert_recognizes new_photo_url, { :path => "photos", :method => :post } h5. The +assert_routing+ Assertion The +assert_routing+ assertion checks the route both ways: it tests that the path generates the options, and that the options generate the path. Thus, it combines the functions of +assert_generates+ and +assert_recognizes+. assert_routing { :path => "photos", :method => :post }, { :controller => "photos", :action => "create" } h3. Changelog "Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/3 * October 4, 2008: Added additional detail on specifying verbs for resource member/collection routes, by "Mike Gunderloy":credits.html#mgunderloy * September 23, 2008: Added section on namespaced controllers and routing, by "Mike Gunderloy":credits.html#mgunderloy * September 10, 2008: initial version by "Mike Gunderloy":credits.html#mgunderloy