Introduction
Routing is the process of mapping incoming requests to the appropriate controller actions in a web application. In Ruby on Rails, routing plays a crucial role in defining how requests are handled and how resources are exposed through the application's API. By defining routes, developers can create a clear and intuitive interface for accessing and modifying data. In order to build robust and scalable web applications, it's essential to have a solid understanding of routing in Ruby on Rails.
Basics of Routing in Rails
Before diving deeper into the routing in rails, this section includes some prerequisites that you need to know.
What is a resource?
In Ruby on Rails, a resource is an object or concept -it could be a databse table for example- that can be accessed and manipulated through a web application's API. A resource keyword in Rails is used to define resourceful routes and controllers that follow a RESTful approach to handling CRUD operations on resources. By using resourceful APIs, developers can create a scalable and maintainable application that enforces a consistent data model and provides a clear separation of concerns.
CRUD Operations
CRUD operations are a set of basic operations that are commonly used to manipulate data in a database or other storage system. CRUD stands for Create, Read, Update, and Delete, and these operations correspond to the basic actions that can be performed on data. In the context of web development, CRUD operations are often used to manipulate resources exposed through an API. By providing a standardized set of operations for accessing and modifying data, CRUD operations help developers create clear and intuitive interfaces for working with data.
How routing works in Rails
In Ruby on Rails, routes and controllers work together to map incoming requests to the appropriate controller actions in a web application. The routes.rb
file is used to define the application's routes, while controllers are responsible for handling the logic and generating the response for each route. Here is an example of a route defined in a routes.rb
file:
Rails.application.routes.draw do
get '/users', to: 'users#index'
end
In this example, a GET
request to the /users
endpoint will be mapped to the index
action in the UsersController
(more on this later in Custom Routes).
Resources keyword
The resources
keyword in Rails is used to define resourceful routes and controllers that provide a RESTful approach to handling CRUD operations on resources. When using the resources
keyword, Rails generates a set of standard routes that map to the standard CRUD actions in the controller. For example:
Rails.application.routes.draw do
resources :photos
end
This will generate seven routes for the Photo
resource: (example from rails routing docuemntation)
HTTP Verb | Path | Controller#Action | Used for |
GET | /photos | photos#index | display a list of all photos |
GET | /photos/new | photos#new | return an HTML form for creating a new photo |
POST | /photos | photos#create | create a new photo |
GET | /photos/:id | photos#show | display a specific photo |
GET | /photos/:id/edit | photos#edit | return an HTML form for editing a photo |
PATCH/PUT | /photos/:id | photos#update | update a specific photo |
DELETE | /photos/:id | photos#destroy | delete a specific photo |
Resource keyword
In contrast, the resource
keyword in Rails is used to define a single resourceful route and controller that provides a RESTful approach to handling CRUD operations on a single resource. The resource
keyword generates a set of standard routes that map to the standard CRUD actions in the controller, but only for a single resource. For example:
Rails.application.routes.draw do
resource :profile
end
This will generate six routes for the Profile
resource:
HTTP Verb | Path | Controller#Action | Used for |
GET | /profile/new | profiles#new | return an HTML form for creating the profile |
POST | /profile | profiles#create | create the new profile |
GET | /profile | profiles#show | display the one and only profile resource |
GET | /profile/edit | profiles#edit | return an HTML form for editing the profile |
PATCH/PUT | /profile | profiles#update | update the one and only profile resource |
DELETE | /profile | profiles#destroy | delete the profile resource |
Limiting generated resources
In Ruby on Rails, the only
keyword can be used with the resources
keyword to limit the set of routes that are generated for a particular resource. In some cases, not all of the generated actions are necessary. The only
keyword can be used to specify a subset of these actions to generate routes for. For example:
Rails.application.routes.draw do
resources :users, only: [:index, :show]
end
This will generate routes for the GET /users
and GET /users/:id
actions, but not for the POST
, PATCH
, or DELETE
actions. By using the only
keyword, developers can create a more focused and streamlined set of routes for each resource, which can help to reduce complexity and improve performance.
Grouping routes
In Ruby on Rails, there are three ways to group routes: using namespaces, scopes, and nested resources. Each grouping method affects the controller prefix and the path in a different way.
Namespace
Namespaces are used to group routes using a common module or path prefix. This creates a new module for the grouped routes, which allows for better encapsulation of the functionality. For example, if you have an admin section for managing products, you might define a namespace like this:
namespace :admin do
resources :products
end
This will create a Admin::ProductsController
controller and views in a separate folder structure (e.g. app/controllers/admin/products_controller.rb
). The path for the routes will be prefixed with /admin
, such as /admin/products
, /admin/products/new
, and /admin/products/:id/edit
.
Scope
Scopes are used to group routes under a common path prefix without creating a new module. This means that the controller prefix and the path will be the same as the rest of the application. For example, if you have a section of your application that requires authentication, you might define a scope like this:
scope :auth do
resources :users
end
This will create a UsersController
controller in the same folder structure as the rest of the application. The path for the routes will be prefixed with /auth
, such as /auth/users
, /auth/users/new
, and /auth/users/:id/edit
.
Nested Resources
Nested resources are used when one resource is related to another resource. This creates a nested hierarchy of routes that reflect the relationships between the resources. For example, if you have a blog website where articles can have comments, you might define nested resources like this:
resources :articles do
resources :comments
end
This will create a CommentsController
controller and views in the same folder structure as the rest of the application. The controller prefix will be the same as the parent resource (ArticlesController
), and the path for the routes will be nested under the parent resource, such as /articles/:article_id/comments
, /articles/:article_id/comments/new
, and /articles/:article_id/comments/:id/edit
.
When deciding which grouping method to use, it's important to consider the level of encapsulation and hierarchy needed for the grouped routes. By choosing the appropriate grouping method, developers can create a clean and maintainable routing structure that is tailored to the specific needs of their application.
Custom Routing
Custom routes in Ruby on Rails allow developers to define their own routes in non-resourceful way. This can be useful when creating custom actions or when you want to define a more specific route for a particular action. To define a custom route, you can specify a path and a controller action using any HTTP verb. For example, suppose you want to create a custom route for a search action in a ProductsController
. You could define it like this:
Rails.application.routes.draw do
get '/products/search', to: 'products#search'
end
This will create a route that maps a GET
request to /products/search
to the search
action in the ProductsController
.
Resourceful vs non-resourceful routes
In Ruby on Rails, resourceful routes are routes that map to resources in the application's data model. They provide a standardized and consistent approach to defining and manipulating resources through a set of HTTP verbs (GET, POST, PUT/PATCH, DELETE) and URL endpoints. Non-resourceful routes, on the other hand, do not map to resources in the data model and are typically used for custom actions or operations that do not fit the standard CRUD model. While non-resourceful routes can be useful in certain situations, they can also make the application's code more difficult to maintain and can lead to inconsistencies in the API. By using resourceful routes, developers can create a clear and intuitive interface for accessing and modifying data, while also enforcing a consistent and predictable data model.
Advanced Routing Concepts
Member vs Collection
In Ruby on Rails, member
and collection
are methods used to define additional actions for specific resources. A member
method is used to apply actions on a specific member (with a certain ID), while a collection
method is used when you need to apply actions on a collection of members. For example, suppose you have a ProductsController
that handles a collection of products. You can use member
to define a custom route that operates on a specific resource, such as a custom_function_member
action. This would apply to a specific product with a certain ID:
resources :products do
member do
get 'custom_function_member'
end
end
Alternatively, you can use collection
to define a custom route that operates on the entire collection of resources, such as a custom_function_collection
action:
resources :products do
collection do
get 'custom_function_collection'
end
end
By using member
and collection
, developers have more flexibility in defining the routing structure of their application, allowing for additional actions to be defined beyond the standard RESTful routes.
Concerns quick overview
In Ruby on Rails, concerns can be used to DRY up (Don't Repeat Yourself) repetitive code in routes. A concern is a reusable group of routes that can be included in other routes. For example, suppose you have a set of routes that are used in multiple controllers. You can define a concern to group these routes together:
concern :searchable do
get 'search', on: :collection
end
Then, you can include this concern in your controllers:
resources :products, concerns: :searchable
resources :users, concerns: :searchable
This will add the search
action to both the products
and users
resources. By using concerns, you can avoid duplicating code and keep your routing structure organized and maintainable.
Scopes within Resources problem
If you try to add a scope inside a resources block, you will face the problem of the scopes path being prepended to the entire path. So for example, if you write the following code:
resource :article do
scope 'admin' do
resources :comments
end
end
You will expect to have the following path: article/admin/comment
However, the generated path is: admin/article/comment
This is a common problem in Ruby on Rails and after referring rails issues on github, using nested do
solves it. So, the following code:
resource :article do
nested do
scope 'admin' do
resources :comments
end
end
end
will generate the following path: article/admin/comment
Conclusion
In conclusion, routing is an essential component of web development and plays a crucial role in defining how requests are handled in a web application. In Ruby on Rails, routing is particularly important, as it helps developers create a clear and intuitive interface for accessing and modifying data. By understanding the basics of routing, including resources, CRUD operations, and how routes and controllers work together, developers can create scalable and maintainable applications that enforce a consistent data model and provide a clear separation of concerns. Grouping routes using namespaces, scopes, and nested resources can help to improve encapsulation and reduce complexity, resulting in more efficient and manageable code. By using the various routing techniques available in Ruby on Rails, developers can create powerful and flexible web applications that meet the needs of their users.
Sources
https://tadhao.medium.com/member-vs-collection-in-rails-routes-ade10c8c5d19#:~:text=Considering the same case%2C the,field required in the route.