RESTful routes in Ruby on Rails

Search icon
Search Book

Let's say that we have a library full of books and we need to build an application to manage these books. Typically we need to perform the following seven operations.

  1. GET the list of all books.
  2. GET detailed information about a particular book.
  3. Click on "Add a new book" and GET a form in response.
  4. POST the values from the form to create a new book.
  5. GET the form with an existing book's data prepopulated so that the user can edit the information about the book.
  6. UPDATE the system with the edited data that was just submitted.
  7. DELETE the book.

These seven actions collectively are known as RESTful representations of the book. We can see that in all the above seven cases, sentences contain the HTTP verbs GET or POST except for operation numbers 6 and 7.

In operation number 6, we are trying to update a particular book. It is possible to use the POST method for updating the records. But we will be following the Rails' standards and will use PUT or PATCH.

The routes for all the seven actions will look like this:

1Rails.application.routes.draw do
2  get '/books', to: "books#index"
3  get '/books/:id', to: "books#show"
4  get '/books/new', to: "books#new"
5  post '/books', to: "books#create"
6  get '/books/:id/edit', to: "books#edit"
7  put '/books/:id', to: "books#update"
8  delete '/books/:id', to: "books#destroy"

Rails resources

Writing all those seven lines of code in the routes file time and again for each item gets repetitive. Rails has put all that under the umbrella term of resources.

If we use resources instead of manually specifying all routes, this is how the routes definition will look like:

1Rails.application.routes.draw do
2  resources :books

Think of resources as a shortcut for not typing all that code. Looking into the mapper.rb file from Rails codebase, we can see that all the seven methods are listed there.

Rails provides a Rake task to list all the routes.

1bundle exec rails routes

As we discussed that one single resources :books is responsible for generating these seven routes.

PrefixVERBURI PatternController#ActionUsed for
booksGET/booksbooks#indexList books
POST/booksbooks#createCreate a book
new_bookGET/books/newbooks#newForm for a new book
edit_bookGET/books/:id/editbooks#editForm for editing book
bookGET/books/:idbooks#showShow info about book
PATCH/books/:idbooks#updateUpdate info about book
PUT/books/:idbooks#updateUpdate info about book
DELETE/books/:idbooks#destroyDelete info about book

If we visit http://localhost:3000/rails/info/routes then also we can see all the routes.

Using only and except to be selective

Sometimes we do not need all the seven routes. For example, let's say that in our application, we don't need to allow users to delete any book. In that case, even if a user sends a DELETE request, we don't want the destroy action to be called.

To do that, we need to tell Rails Routing not to have any routing for DELETE verb:

1resources :books, except: :destroy

Here is another example:

1resources :books, only: %i[index show]

In this case, only index and show actions are added to routing. That is, a user can see the list of books and can get details about a book. But they can't create, edit or delete a book.

Difference between singular resource and resources in Rails routes

So far, we have been using resources to declare a resource. Rails also lets us declare a singular version of it using resource.

Rails recommends us to use singular resource when we do not have an identifier. For example, the URL for the profile page is, typically, /profile and not /profile/495. In such cases, we do not need certain routes.

For example, if we define a resource like this in config/routes.rb:

1resource :profile

The routes generated by singular resource will be:

PrefixVERBURI PatternController#Action

Notice that the URL pattern is using the singular style. URLs are /profile/new and not /profiles/new.

Singular resources are always named singularly whereas plural resources have a plural name.

However, the controller name is still plural. This was a subject of great debate within the Rails community. The community decided to keep the controller name plural for both singular and multiple resources.

Here is an explanation from the Rails official guide:

Because you might want to use the same controller for a singular route (/account) and a plural route (/accounts/45), singular resources map to plural controllers. So that, for example, resource :photo and resources :photos creates both singular and plural routes that map to the same controller (PhotosController).

Notice that for the show action, the corresponding URL is just /profile. No identifier is needed. The same is true for creating, updating, and deleting the profile.

In this case, for all these actions, we will be performing the operations on the currently logged-in user's data. Therefore we don't need an identifier for those actions.

Finally, there is no index action since here we are talking about a singular resource.

Another common usage of the singular resource is when we are dealing with login operations. When a user logs in, we need to maintain a session. For each logged-in user, there could be only one session.

In that case, our routes definition will look like this:

1resource :session, only: [:new, :create, :destroy]

Controller could look like this:

1class SessionsController < ApplicationController
2  def new
3    # logic
4  end
6  def create
7    # logic
8  end
10  def destroy
11    # logic
12  end

Adding more RESTful actions

You are not limited to the seven routes that RESTful routing creates by default. If need arises, Rails allows you to add additional routes that apply to the collection or individual members of the collection.

You can add two types of routes to a RESTful resource. These are called collection and member routes.

Collection routes

Collection routes apply to the whole collection. For example, if a RESTful resource called books represents a collection of book objects then a collection route called "destroy_multiple" will create the books/destroy_multiple path.

A collection route can be added like so:

1resources :books do
2  collection do
3    get "delete_multiple"
4  end

The above code will add a delete_multiple RESTful action in the books_controller and GET requests on books/destroy_multiple will be routed to the delete_multiple action.

You can use get, patch, put, post, or delete HTTP verbs here. If you do not have multiple collection routes, you can also pass :on to a route, eliminating the block like so:

1resources :books do
2  get "delete_multiple", on: :collection

Member routes

A member route applies to a member of the collection. For example if a RESTful resource called users represents a collection of user objects and each user object can be represented by user/:id where id is the resource identifier for a user object, then a member route called "report" will create the users/:id/report path.

A member route can be added like so:

1resources :users do
2  member do
3    get "report"
4  end

The above code will add a report RESTful action in the users_controller and GET requests on users/:id/destroy_multiple will be routed to the report action.

Just as with member routes, you can pass :on to eliminate the block like so:

1resources :users do
2  get "ban", on: :member

Collection routes should be added when a CRUD operation needs to be performed on a collection of objects whereas a member route should be used when the CRUD operation concerns a single object from a collection of objects.

To learn about routing in depth, you can refer to the in-depth chapter about Rails routing.

There shouldn't be any changes to the application in this chapter. So let us clean up any accidental changes.

1git clean -fd