‘Iacta alea est’
In Framework Abstractions: HTTP I looked at how, in Ruby, the Rack library abstracts HTTP (and CGI) in a faithful manner, providing all of the necessary details in a Rack::Request
object. Almost all Ruby web frameworks conform to the Rack specification and build their routing mechanisms on top of it.
A routing mechanism is essentially a mapper (router) that maps (routes) a given HTTP request to the corresponding and appropriate endpoint, controller or section of code that is designed to handle that particular request. It is the entry point for the transformation of an HTTP request into some or other action in, or response from, your web application.
I’d like to compare two contrasting approaches to routing mechanisms in two Ruby web frameworks: Ruby on Rails and Syro. Since routing is common to all web applications, this is prime territory for a framework to abstract away the lower-level details of the HTTP request so that we can start building at a higher level.
Rails is by far and away the most popular web framework written in Ruby. It could even be argued that without Rails, Ruby would never have found its way into the mainstream. Rails has a built-in router that relies on a route definition file (config/routes.rb
) which loads this set of routes into memory when the application is booted.
Rails also, as would be expected in a framework with a doctrine of convention over configuration, builds heavily on REST-ful routes or resource-oriented routing. That is, building loosely on Roy Fielding’s dissertation on REpresentational State Transfer as a means of using the stateless HTTP protocol to architect applications, Rails maps common HTTP actions to operations on a given resource, which normally takes the form of an entity stored in a database row whose table corresponds to that resource type.
In concrete terms, a resource could be a book and would have a database table to store different books. The standard create, read, update, delete actions (CRUD) can be executed in the database using SQL queries. The router then provides a way to map certain HTTP requests to these different actions in a predictable manner. The actions themselves in Rails are methods (called actions) in a given controller for that resource.
HTTP Verb Route Database Controller
------------------------------------------------
POST /books Create create
GET /books/:id Read show
PUT/PATCH /books/:id Update update
DELETE /books/:id Delete destroy
On top of that Rails also provides routes for ancillary actions, only one of which has a corresponding operation involving the database:
HTTP Verb Route Database Controller
------------------------------------------------
GET /books/new - new
GET /books/:id/edit - edit
GET /books Read index
Now, all of that context is required to understand the core of Rails’s routing configuration. For a given resource, e.g. books, creating a mapping for each of these seven URLs is as easy as defining the following in the routes file:
resources :books
It can be modified to either exclude certain actions, or include only certain actions by passing some options:
resources :books, only: [:show, :create, :update, :destroy]
resources :books, except: [:new, :edit, :index]
Additional routes and actions can be defined on the resource by passing a block with the member
or collection
method:
resources :books do
collection do
get 'search'
end
member do
delete 'avatar'
options '…'
patch '…'
post '…'
put '…'
end
end
These allow adding additional routes to the resource using provided methods to HTTP verbs. For non-resource routes these HTTP verb methods can be used to create one-off routes and map them to a given controller and action:
get 'search', to: 'books#search'
delete 'avatar', to: 'books#destroy_avatar'
Or you can use the generic match
method while specifying the HTTP verb(s):
match 'avatar', to: 'books#avatar', via: [:get, :delete]
The to: 'books#avatar
syntax will map the given URL (/avatar
) to the BooksController
and avatar
action. Here the mapping is explicit compared to resources
implicit map.
I don’t have time to go into the details of namespace
, scope
, constraints
, controller
, nested
, shallow
, direct
, resolve
and concern
. They are all additional methods designed to help you manage your routing. If you want you can go read the guide.
So in Rails, all of these different methods are tools for mapping the URL to a particular controller and a particular action in your application. It attempts to provide an easy and concise way to define where each HTTP Request and corresponding URL should go.
—Wednesday 10th February 2021.