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.
- GET the list of all books.
- GET detailed information about a particular book.
- Click on "Add a new book" and GET a form in response.
- POST the values from the form to create a new book.
- GET the form with an existing book's data prepopulated so that the user can edit the information about the book.
- UPDATE the system with the edited data that was just submitted.
- 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" 9end
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 3end
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.
|Prefix||VERB||URI Pattern||Controller#Action||Used for|
|POST||/books||books#create||Create a book|
|new_book||GET||/books/new||books#new||Form for a new book|
|edit_book||GET||/books/:id/edit||books#edit||Form for editing book|
|book||GET||/books/:id||books#show||Show info about book|
|PATCH||/books/:id||books#update||Update info about book|
|PUT||/books/:id||books#update||Update info about book|
|DELETE||/books/:id||books#destroy||Delete 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:
The routes generated by singular resource will be:
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 5 6 def create 7 # logic 8 end 9 10 def destroy 11 # logic 12 end 13end
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 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 5end
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 3end
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 5end
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 3end
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