Learn Ruby on Rails Book

RESTful routes in Ruby on Rails

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 following seven operations.

  1. GET the list of the books.
  2. GET information about a particular book.
  3. GET a form where information about a new book can be entered.
  4. POST the form to add a new book to the system.
  5. GET the form where the information about an existing book is prepopulated so that user can edit the information about the book.
  6. UPDATE the system with the edited data that was just submitted.
  7. DELETE one particular book.

These seven actions collectively are known as RESTful representation of the book. We can see that in the all the above seven cases sentences starts with HTTP verb GET or POST except for item number 6 & 7.

In the item number 6 we are trying to update a particular book. We can use POST to update the record but Rails has decided to support both PUT and PATCH for updating.

Let's write routes for all the seven actions. Let's assume that we have a controller named BooksController.

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 'book/edit',    to: "books#edit"
7  put 'book/update',  to: "books#update"
8  delete 'books/:id', to: "books#destroy"
9end

The BooksController would look something like this.

1class BooksController < ApplicationController
2  def index
3  end
4
5  def show
6  end
7
8  def new
9  end
10
11  def edit
12  end
13
14  def create
15  end
16
17  def update
18  end
19
20  def destroy
21  end
22end

Rails resources

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

Here is a version without using resources.

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 'book/edit',    to: "books#edit"
7  put 'book/update',  to: "books#update"
8  delete 'books/:id', to: "books#destroy"
9end

Here is how the code would look if we use resources. Note that both the code do almost same thing.

1Rails.application.routes.draw do
2  # delete all the lines and add just the following line
3  resources :books
4end

Think of resources as a shortcut for not typing all that code. If we look at mapper.rb code in Rails codebase we can see all the seven methods listed there. Now let's see what the routes look like when we are using resources.

1bundle exec rake 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/:idbooks#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

In the above table we see columns "Prefix" which we will discuss in a different chapter.

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 do not have capability to delete any book. If a user sends a DELETE request then we should not send that request to any controller. In order to do that we need to tell Rails Routing to not to have any routing for DELETE verb.

1resources :books, except: [:destroy]

Here is another example.

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

In the above case user can only see list of books and can get information about a book. User can't create, edit or delete a book.

Use scaffold to build articles controller

Let's put what we have learned in action. Execute following commands on terminal.

1bundle exec rails generate scaffold articles name:string desc:text
2bundle exec rails db:migrate
3bundle exec rails server

Now visit http://localhost:3000/rails/info/routes. We will see a bunch of routes. We can also see these routes by executing following command.

1bundle exec rake routes

The output should look familiar.

Difference between singular resource and resources in Rails routes

So far we have been using resources. Rails also provides singular version of it called resource.

Rails recommends us to use singular resource when we do not have an identifier. For example typically the url for profile page is /profile and not /profile/495. In such cases we do not need certain routes. Let's see it in action. Open config/routes.rb and add following line.

1resource :profile

Now let's see the routes that are generated.

1bundle exec rake routes
PrefixVERBURI PatternController#Action
new_profileGET/profile/newprofiles#new
edit_profileGET/profile/editprofiles#edit
profileGET/profileprofiles#show
PATCH/profileprofiles#update
PUT/profileprofiles#update
DELETE/profileprofiles#destroy
POST/profileprofiles#create

Notice that the URL pattern is using singular style. URLs are /profile/new and not /profiles/new. However the controller name is still plural. This was subject of a great debate within the Rails community. However the community has decided to keep the controller name plural whether it is a singular resource or a multiple resource.

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. Same for deleting the profile and for creating and updating the profile.

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

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

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

Controller could look like this.

1class SessionsController < ApplicationController
2  def new
3  end
4
5  def create
6  end
7
8  def destroy
9  end
10end

We are not going to check all these changes.

1git clean -fd