8.3. RESTful Routes

REST (REpresentational State Transfer) was already introduced earlier in the book when you developed the basic blog application. This section briefly recaps how this new paradigm affects Rails' routing.

RESTful routing is considered to be the new standard for Rails applications and, whenever possible, it should be favored over the traditional style described so far. In short, RESTful routing doesn't simply match a URL to code within the controller, but rather maps resource identifiers (think URLs) and HTTP verbs to seven predefined actions. These actions normally perform CRUD operations in the database as well, through the model layer.

8.3.1. map.resources

RESTful routes can be defined through the resources method. Consider this route:

map.resources :books

Whenever you need to declare more than one resource, you can do so on a single line by passing a list of symbols (for example, map.resources :books, :catalogs, :users) to resources.

This manages to pull off a fair bit of magic, by abstracting and hiding many of REST's implementation details, as well as providing you with named routes and easy-to-use helpers to work with. A single concise line creates seven RESTful routes, as shown in the following table.

Route NameHTTP MethodURLAction in BooksController
booksGET/booksindex
formatted_booksPOST/bookscreate
new_bookGET/books/newnew
bookGET/books/:idshow
edit_bookGET/books/:id/editedit
formatted_bookPUT/books/:idupdate
formatted_bookDELETE/books/:iddestroy

Each named route will also be accessible when needed through the _url and _path helpers (four pairs are generated excluding the ones starting with formatted_).

NOTE

Formatted routes are going to be removed from Rails 2.3 because they are rarely used and have an impact on the memory footprint. As such, the formatted helpers will disappear as well; they will be replaced by a :format parameter passed to regular route helpers (for example, books_path(:format => "xml")).

Keep in mind that in reality the number of routes generated is more than seven because a counterpart that includes a :format parameter exists for all of the routes listed in the preceding table. The whole REST paradigm is founded on the idea that for a given resource there can be several "Representations." This means that in addition to the ones already listed, you also have the routes shown in the following table.

Route NameHTTP MethodURLAction in BooksController
formatted_booksGET/books.:formatindex
formatted_booksPOST/books.:formatcreate
formatted_new_bookGET/books/new.:formatnew
formatted_bookGET/books/:id.:format 
formatted_edit_bookGET/books/:id/edit:formatedit
formatted_bookPUT/books/:id.formatupdate
formatted_bookDELETE/books/:id.:formatdestroy

The value of the id will of course be accessible through params[:id](as it is with any other route), and params[:format] will do the trick for the format. However, as you've seen before, the respond_to method spares you from having to worry about retrieving the format through the params hash-like object.

The request GET /books will be handled by the index action, but POST /books will be mapped with the create action. Similarly, GET /books.xml will still be handled by the index action, but the format of the request will be XML, and the action should handle this accordingly, by providing an XML response.

Because the HTTP verb is meaningful and used by the routing subsystem, to destroy a resource it won't be enough to simply reach a URL. You have to explicitly send a DELETE request. In practice this means that you'll be provided with further protection from accidental, potentially damaging operations. To illustrate this point, think of Web crawlers. They follow links — smart ones may even dynamically come up with requests — but they definitely do not send DELETE requests to your Web application.

NOTE

If you don't require all of the seven actions, you can save memory resources by indicating that only some routes need to be generated or excluded. The options for this are :only and :except (for example, map.resources :books, :only => [:index, :show]).

8.3.2. map.resource

Defining a series of routes through resources assumes that an index action is going to show a list of resources, while other actions allow you to show, edit, create, and destroy a single resource. The resource method exists for times when you need to work with a singular resource (known as singleton resource), as opposed to a collection of resources. The symbol passed to the method needs to be singular as well, whereas the corresponding controller is by default pluralized:

map.resource :catalog

The generated routes are slightly different and all singular as shown by running rake routes:

POST   /catalog               {:controller=>"catalogs", :action=>"create"}
        POST   /catalog.:format       {:controller=>"catalogs", :action=>"create"}
new_catalog
        GET    /catalog/new           {:controller=>"catalogs", :action=>"new"}
formatted_new_catalog

GET    /catalog/new.:format   {:controller=>"catalogs", :action=>"new"}
edit_catalog
        GET    /catalog/edit          {:controller=>"catalogs", :action=>"edit"}
formatted_edit_catalog
        GET    /catalog/edit.:format  {:controller=>"catalogs", :action=>"edit"}
catalog
         GET    /catalog              {:controller=>"catalogs", :action=>"show"}
formatted_catalog
         GET    /catalog.:format      {:controller=>"catalogs", :action=>"show"}
         PUT    /catalog              {:controller=>"catalogs", :action=>"update"}
         PUT    /catalog.:format      {:controller=>"catalogs", :action=>"update"}
         DELETE /catalog              {:controller=>"catalogs", :action=>"destroy"}
         DELETE /catalog.:format      {:controller=>"catalogs", :action=>"destroy"}

Visualizing routes through the console is admittedly lame. For projects with complex routes, consider installing the Vasco plugin, which is a route explorer available at http://github.com/relevance/vasco.

GET /catalog will show the singleton resource (that doesn't have to be identified by an id, being the only one globally identified) and no index action is mapped by any of the routes either, because there is only one catalog and hence you don't need a list.

8.3.3. Customizing RESTful Routes

The routes defined by resources can be customized by passing a hash of additional parameters to the method. Many of these can be used with resource as well. Aside from :only and :except, as mentioned earlier (introduced in Rails 2.2), these are as follows.

8.3.3.1. :as

The :as option specifies a different name to be used for the path of each generated route. Consider the following:

map.resources :books, :as => "titles"

This will generate the routes shown in the previous tables, but with the path being titles not books. Therefore GET /titles/42 will be mapped with the action show of the BooksController. This option is often used when the resource name contains an underscore, but you'd like the URLs to contain a dash instead. It's important to notice that the *_url and *_path helpers generated are still based on the name of the resource; so, for example, you still have new_book_url and new_book_path and not new_title_url and new_title_path.

8.3.3.2. :collection, :member, and :new

These three options are used when you need to add routes to map to any action other than the seven default ones. :collection specifies one or more named routes for actions that work on a collection resource:

map.resources :books, :collection => { :search => :get }

This will recognize GET /books/search requests and even generate the search_books_url and search_books_path helpers (as well as the two corresponding helpers obtained by appending formatted). The acceptable values for the HTTP method are :get, :post, :put, :delete, and :any. When the HTTP verb of the request doesn't matter, you can use :any.

:member is very similar, but is reserved for actions that operate on a single member of the collection:

map.resources :books, :member => { :borrow => :post }

This will generate the route helpers to access the borrow_book named route. Finally, :new is used to define routes for creating a new resource:

map.resources :books, :new => { :register => :post }

Requests such as POST /books/new/register will be recognized and handled by the register action of the BooksController. The named routes generated will be register_new_books and formatted_register_new_books. Note that if you'd like to alter the accepted verbs mapping to the existing new method, you can do so through the :new parameter as well by, for example, binding the new action to the :any symbol representing any verb:

map.resources :books, :new => { :new => :any, :register => :post }

8.3.3.3. :conditions

The :conditions parameter was introduced first in this chapter as an option for the method connect. Because this defines HTTP verb restrictions on single routes, it's hardly needed in a RESTful context.

8.3.3.4. :controller

The :controller option is used to specify a different controller for the generated routes. For example, consider the following:

map.resources :books, :controller => "titles"

When a request for GET /books comes in, Rails knows that the index action of the TitlesController should handle the request. The named routes, and therefore the helpers, generated are going to be based on the name of the resource, and will therefore not be affected.

8.3.3.5. :has_one and :has_many

:has_one and :has_many are two options used as a shorthand to declare basic nested resources. The difference between the two is that :has_one maps singleton resources rather than plural ones.

Consider this:

map.resources :books, :has_one => :author, :has_many => [:reviews, :categories] Notice how multiple resources are assigned through an array of symbols.

This is equivalent to declaring the nested routes as follows:

map.resources :books do |book|
  book.resource :author

book.resources :reviews
  book.resources :categories
end

This second, more explicit, notation enables you to create resources that are nested within resources, which in turn are nested within resources and so on to create a hierarchy that's as deep as you want it to be. But don't be tempted to fall in this trap. A deeply nested resource will be accessible through very long URLs and helpers that are no longer easy to remember or type. The consensus within the Rails community is that one level of nested routes, as demonstrated in Chapter 6 for the comments resource, is an acceptable compromise.

Technically, there is also a :shallow => true option that can be passed to resources before the block, to indicate that the routes/helpers declared within the block can be used without prefixing the name of the resource that contains them (or any :name_prefix or :path_prefix specified) when accessing an individual resource through its id.

To learn more about this and other options, check the online documentation and consider reading the official guide "Rails Routing from the Outside In," available at http://guides.rails.info/routing/routing_outside_in.html. Most of the material is covered in this chapter, but you may find a few extra bits of specialized information there.

Generating Rails Guides

Rails guides are available online, but you can also generate all of Rails' official guides by running rake doc:guides from within your Rails project. This task will place the guides within the docguides folder. Upon launching index.html within your browser, you will be able to select the guide that interests you among the ones shown (as you can see in Figure 8-1.

Currently these are "Getting Started with Rails," "Rails Database Migrations," "Active Record Associations," "Active Record Finders," "Layouts and Rendering in Rails," "Action View Form Helpers," "Rails Routing from the Outside In," "Basics of Action Controller," "Rails Caching," "Testing Rails Applications," "Securing Rails Applications," "Debugging Rails Applications," "Benchmarking and Profiling Rails Applications," and "The Basics of Creating Rails Plugins." New guides are likely to be published in the future.

Reading these guides will solidify, integrate, and augment the concepts presented within this book.


8.3.3.6. Other Options

If all the options mentioned so far are not enough for what you need to do, a few further options are available. :name_prefix and :path_prefix, respectively, are used to add a string prefix (for example, "book_") and for adding a prefix that includes variables in the same format as the pattern passed to connect (for example, "/publisher/:publisher_id") to the generated routes.

:singular (not available for resource) and :plural are used to specify the correct singularization and pluralization of the resource name. Two other options, already discussed for connect, are :requirements and arbitrarily named parameters (previously indicated with the generic name :<parameter_name>).

Figure 8.1. Figure 8-1

8.3.3.7. Namespaced Resources

Rails supports the so-called namespaced resources. These are resources that are consistently prefixed by a given string. ActionController defines the method namespace that automatically prefixes the generated routes for you. For example, consider the following:

map.namespace(:admin) do |admin|
  admin.resources :books
end

There are quite a few implications to this:

  • Each book-related route will now expect the incoming URL to be prefixed by admin. For instance, to access the list of books, you'll need a GET /admin/books request.

  • The controller is now expected to be located within the appcontrollersadmin folder and therefore have its name prefixed by Admin::. In this case, the controller name needs to be changed to Admin::BooksController.

  • If you generate the BooksController by scaffolding, you will be surprised to see that the application no longer works. This is because now all the helpers used by the application to access book-related routes are going to be prefixed by admin (for example, admin_books_url).

Despite the adjustments that are required, it's often a good idea to group a few controllers within a common namespace. The most usual scenario is indeed grouping a series of controllers that belong to an administration section.

The same results can be accomplished by carefully combining :name_prefix and :path_prefix, but there is really no reason to do so if namespaced routes fit the bill.

8.3.3.8. Helpers and RESTful Routes

Besides being able to generate routes in a concise manner, RESTful routes also have an advantage when it comes to working with helpers. Consider the following method calls, taken from the blog application:

redirect_to(@article)
redirect_to([@article, @comment])
redirect_to(article_comments_url(@article))
<%= link_to 'Edit', edit_article_path(article) %>
<%= link_to 'Show', [@article, @comment] %>

Notice how these methods are able to understand the URL that needs to be generated, by simply passing objects to them. This magic is possible thanks to the usual Convention over Configuration principle. Helpers are "smart" and understand that passing an object (for example, @article) means that you want to display that resource, and therefore the URL should be generated by using the corresponding controller name and the id of that object (for example, a GET request for /articles/12).

The same helpers are also aware of nested routes, and [@article, @comment] are automatically mapped, in this specific case, to paths like /articles/12/comments/3. That array is essentially telling the method it's passed to (for example, redirect_to or link_to) that you want to show that comment (for example, with id 3); keep in mind the fact that it's nested within an article resource (for example, with id 12).

You can consider this notation to be a shorthand version of some of the more verbose *_url and *_path helpers.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.191.134