From 591e87fe7b10ed4c90f65ff25bb39520cdad0f87 Mon Sep 17 00:00:00 2001 From: Joost Baaij Date: Sun, 29 Aug 2010 00:04:14 +0200 Subject: [PATCH] Expanded routing documentation with current best practices --- .../lib/action_dispatch/http/response.rb | 19 +- .../lib/action_dispatch/routing/mapper.rb | 169 ++++++++++++++++++ .../routing/polymorphic_routes.rb | 2 +- .../lib/action_dispatch/routing/url_for.rb | 9 +- .../action_dispatch/testing/integration.rb | 10 +- 5 files changed, 188 insertions(+), 21 deletions(-) diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 3b85a98576..ff5e96fdf7 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -4,27 +4,26 @@ require 'active_support/core_ext/class/attribute_accessors' module ActionDispatch # :nodoc: - # Represents an HTTP response generated by a controller action. One can use - # an ActionDispatch::Response object to retrieve the current state - # of the response, or customize the response. An Response object can - # either represent a "real" HTTP response (i.e. one that is meant to be sent - # back to the web browser) or a test response (i.e. one that is generated - # from integration tests). See CgiResponse and TestResponse, respectively. + # Represents an HTTP response generated by a controller action. Use it to + # retrieve the current state of the response, or customize the response. It can + # either represent a real HTTP response (i.e. one that is meant to be sent + # back to the web browser) or a TestResponse (i.e. one that is generated + # from integration tests). # - # Response is mostly a Ruby on Rails framework implement detail, and + # \Response is mostly a Ruby on \Rails framework implementation detail, and # should never be used directly in controllers. Controllers should use the # methods defined in ActionController::Base instead. For example, if you want # to set the HTTP response's content MIME type, then use # ActionControllerBase#headers instead of Response#headers. # # Nevertheless, integration tests may want to inspect controller responses in - # more detail, and that's when Response can be useful for application + # more detail, and that's when \Response can be useful for application # developers. Integration test methods such as # ActionDispatch::Integration::Session#get and # ActionDispatch::Integration::Session#post return objects of type - # TestResponse (which are of course also of type Response). + # TestResponse (which are of course also of type \Response). # - # For example, the following demo integration "test" prints the body of the + # For example, the following demo integration test prints the body of the # controller response to the console: # # class DemoControllerTest < ActionDispatch::IntegrationTest diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 9a92ed0b62..a3bd4771c2 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -226,10 +226,24 @@ def initialize(set) #:nodoc: @set = set end + # You can specify what Rails should route "/" to with the root method: + # + # root :to => 'pages#main' + # + # You should put the root route at the end of config/routes.rb. def root(options = {}) match '/', options.reverse_merge(:as => :root) end + # When you set up a regular route, you supply a series of symbols that + # Rails maps to parts of an incoming HTTP request. + # + # match ':controller/:action/:id/:user_id' + # + # Two of these symbols are special: :controller maps to the name of a + # controller in your application, and :action maps to the name of an + # action within that controller. Anything other than :controller or + # :action will be available to the action as part of params. def match(path, options=nil) mapping = Mapping.new(@set, @scope, path, options || {}).to_route @set.add_route(*mapping) @@ -258,22 +272,29 @@ def default_url_options=(options) end module HttpHelpers + # Define a route that only recognizes HTTP GET. def get(*args, &block) map_method(:get, *args, &block) end + # Define a route that only recognizes HTTP POST. def post(*args, &block) map_method(:post, *args, &block) end + # Define a route that only recognizes HTTP PUT. def put(*args, &block) map_method(:put, *args, &block) end + # Define a route that only recognizes HTTP DELETE. def delete(*args, &block) map_method(:delete, *args, &block) end + # Redirect any path to another path: + # + # match "/stories" => redirect("/posts") def redirect(*args, &block) options = args.last.is_a?(Hash) ? args.pop : {} @@ -314,12 +335,72 @@ def map_method(method, *args, &block) end end + # You may wish to organize groups of controllers under a namespace. + # Most commonly, you might group a number of administrative controllers + # under an +admin+ namespace. You would place these controllers under + # the app/controllers/admin directory, and you can group them together + # in your router: + # + # namespace "admin" do + # resources :posts, :comments + # end + # + # This will create a number of routes for each of the posts and comments + # controller. For Admin::PostsController, Rails will create: + # + # GET /admin/photos + # GET /admin/photos/new + # POST /admin/photos + # GET /admin/photos/1 + # GET /admin/photos/1/edit + # PUT /admin/photos/1 + # DELETE /admin/photos/1 + # + # If you want to route /photos (without the prefix /admin) to + # Admin::PostsController, you could use + # + # scope :module => "admin" do + # resources :posts, :comments + # end + # + # or, for a single case + # + # resources :posts, :module => "admin" + # + # If you want to route /admin/photos to PostsController + # (without the Admin:: module prefix), you could use + # + # scope "/admin" do + # resources :posts, :comments + # end + # + # or, for a single case + # + # resources :posts, :path => "/admin" + # + # In each of these cases, the named routes remain the same as if you did + # not use scope. In the last case, the following paths map to + # PostsController: + # + # GET /admin/photos + # GET /admin/photos/new + # POST /admin/photos + # GET /admin/photos/1 + # GET /admin/photos/1/edit + # PUT /admin/photos/1 + # DELETE /admin/photos/1 module Scoping def initialize(*args) #:nodoc: @scope = {} super end + # Used to route /photos (without the prefix /admin) + # to Admin::PostsController: + # + # scope :module => "admin" do + # resources :posts + # end def scope(*args) options = args.extract_options! options = options.dup @@ -441,6 +522,37 @@ def override_keys(child) end end + # Resource routing allows you to quickly declare all of the common routes + # for a given resourceful controller. Instead of declaring separate routes + # for your +index+, +show+, +new+, +edit+, +create+, +update+ and +destroy+ + # actions, a resourceful route declares them in a single line of code: + # + # resources :photos + # + # Sometimes, you have a resource that clients always look up without + # referencing an ID. A common example, /profile always shows the profile of + # the currently logged in user. In this case, you can use a singular resource + # to map /profile (rather than /profile/:id) to the show action. + # + # resource :profile + # + # It's common to have resources that are logically children of other + # resources: + # + # resources :magazines do + # resources :ads + # end + # + # You may wish to organize groups of controllers under a namespace. Most + # commonly, you might group a number of administrative controllers under + # an +admin+ namespace. You would place these controllers under the + # app/controllers/admin directory, and you can group them together in your + # router: + # + # namespace "admin" do + # resources :posts, :comments + # end + # module Resources # CANONICAL_ACTIONS holds all actions that does not need a prefix or # a path appended since they fit properly in their scope level. @@ -549,6 +661,24 @@ def resources_path_names(options) @scope[:path_names].merge!(options) end + # Sometimes, you have a resource that clients always look up without + # referencing an ID. A common example, /profile always shows the + # profile of the currently logged in user. In this case, you can use + # a singular resource to map /profile (rather than /profile/:id) to + # the show action: + # + # resource :geocoder + # + # creates six different routes in your application, all mapping to + # the GeoCoders controller (note that the controller is named after + # the plural): + # + # GET /geocoder/new + # POST /geocoder + # GET /geocoder + # GET /geocoder/edit + # PUT /geocoder + # DELETE /geocoder def resource(*resources, &block) options = resources.extract_options! @@ -578,6 +708,22 @@ def resource(*resources, &block) self end + # In Rails, a resourceful route provides a mapping between HTTP verbs + # and URLs and controller actions. By convention, each action also maps + # to particular CRUD operations in a database. A single entry in the + # routing file, such as + # + # resources :photos + # + # creates seven different routes in your application, all mapping to + # the Photos controller: + # + # GET /photos/new + # POST /photos + # GET /photos/:id + # GET /photos/:id/edit + # PUT /photos/:id + # DELETE /photos/:id def resources(*resources, &block) options = resources.extract_options! @@ -608,6 +754,18 @@ def resources(*resources, &block) self end + # To add a route to the collection: + # + # resources :photos do + # collection do + # get 'search' + # end + # end + # + # This will enable Rails to recognize paths such as /photos/search + # with GET, and route to the search action of PhotosController. It will also + # create the search_photos_url and search_photos_path + # route helpers. def collection unless @scope[:scope_level] == :resources raise ArgumentError, "can't use collection outside resources scope" @@ -618,6 +776,17 @@ def collection end end + # To add a member route, add a member block into the resource block: + # + # resources :photos do + # member do + # get 'preview' + # end + # end + # + # This will recognize /photos/1/preview with GET, and route to the + # preview action of PhotosController. It will also create the + # preview_photo_url and preview_photo_path helpers. def member unless resource_scope? raise ArgumentError, "can't use member outside resource(s) scope" diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index 31dba835ac..acebde6ede 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -17,7 +17,7 @@ module Routing # # == Usage within the framework # - # Polymorphic URL helpers are used in a number of places throughout the Rails framework: + # Polymorphic URL helpers are used in a number of places throughout the \Rails framework: # # * url_for, so you can use it with a record as the argument, e.g. # url_for(@article); diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index ba93ff8630..28ec830fe8 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -1,6 +1,6 @@ module ActionDispatch module Routing - # In routes.rb one defines URL-to-controller mappings, but the reverse + # In config/routes.rb you define URL-to-controller mappings, but the reverse # is also possible: an URL can be generated from one of your routing definitions. # URL generation functionality is centralized in this module. # @@ -12,15 +12,14 @@ module Routing # # == URL generation from parameters # - # As you may know, some functions - such as ActionController::Base#url_for + # As you may know, some functions, such as ActionController::Base#url_for # and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set # of parameters. For example, you've probably had the chance to write code # like this in one of your views: # # <%= link_to('Click here', :controller => 'users', # :action => 'new', :message => 'Welcome!') %> - # - # # Generates a link to /users/new?message=Welcome%21 + # # => "/users/new?message=Welcome%21" # # link_to, and all other functions that require URL generation functionality, # actually use ActionController::UrlFor under the hood. And in particular, @@ -61,7 +60,7 @@ module Routing # # UrlFor also allows one to access methods that have been auto-generated from # named routes. For example, suppose that you have a 'users' resource in your - # routes.rb: + # config/routes.rb: # # resources :users # diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index b52795c575..590ebbf364 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -115,8 +115,8 @@ def delete_via_redirect(path, parameters = nil, headers = nil) end end - # An integration Session instance represents a set of requests and responses - # performed sequentially by some virtual user. Because you can instantiate + # An instance of this class represents a set of requests and responses + # performed sequentially by a test process. Because you can instantiate # multiple sessions and run them side-by-side, you can also mimic (to some # limited extent) multiple simultaneous users interacting with your system. # @@ -373,12 +373,12 @@ def method_missing(sym, *args, &block) end end - # An IntegrationTest is one that spans multiple controllers and actions, + # An test that spans multiple controllers and actions, # tying them all together to ensure they work together as expected. It tests # more completely than either unit or functional tests do, exercising the # entire stack, from the dispatcher to the database. # - # At its simplest, you simply extend IntegrationTest and write your tests + # At its simplest, you simply extend IntegrationTest and write your tests # using the get/post methods: # # require "test_helper" @@ -403,7 +403,7 @@ def method_missing(sym, *args, &block) # However, you can also have multiple session instances open per test, and # even extend those instances with assertions and methods to create a very # powerful testing DSL that is specific for your application. You can even - # reference any named routes you happen to have defined! + # reference any named routes you happen to have defined. # # require "test_helper" # -- GitLab