RESTful routes for all the things
Today I thought it would be fun to consider what happens to a code base when a team decides to strictly adhere to a RESTful architecture.
Before we begin, let's talk about what REST is.
Representational state transfer (REST)
Per wikipedia, Representational state transfer (commonly abbreviated as REST) is "a software architectural style that was created to guide the design and development of the architecture for the World Wide Web". In other words, it's a style guide for writing code within its constraints. Applications that follow this style guide are said to be "RESTful".
For our purposes in Rails, RESTful architecture involves building controllers that only use the default actions that are generated when you use something like resources :articles
to define routes in your application.
These seven actions are as follows:
index
- used for displaying a list of a resource (i.e./articles
)show
- used for displaying a single resource (i.e./articles/1
)new
- used for building a new resource (i.e./articles/new
)create
- used for persisting a new resourceedit
- used for changing a resource (i.e./articles/1/edit
)update
- used for persisting changes to a resourcedestroy
- used for deleting a resource
This seems too limited. What gives?
Now, you might be thinking, there's no way these seven actions can cover all of the business logic in an application. And I would agree under the condition where we have one controller per resource (or model
) in our application.
For example, let's say our Article
model has an upvote_count
column for storing likes, and we have a feature request to build out the user experience for incrementing that column.
If we're already using ArticlesController#update
to allow the author to update the entire article, then how can we update this column without ending up with a bunch of conditional statements (which increase complexity significantly) in our update
method?
Create smaller controllers that do not map to your resources one-to-one
Controllers, after all, are merely a liason between our application and it's data layer. They do not need to map to our models one-to-one.
Let's shift our thinking and try considering the user action as the resource. In this case, a user is upvoting an Article
. What if created a brand new controller for this action instead of trying to jam this into our ArticlesController#update
method or creating a non-RESTful route like ArticlesController#upvote
?
We might end up with something like this:
# config/routes.rb
resources :articles do
resources :upvotes, only: [:create]
end
class UpvotesController < ApplicationController
def create
@article = Article.find(params[:article_id])
if @article.increment!(:upvote_count)
redirect_to @article
end
end
It might feel weird using a create
action to update something on the backend, but I assure that this is perfectly reasonable.
Impacts on our code base
Developing Rails applications that adhere to REST as close as possible has led to code that is easier to maintain in my experience. The limitations force you to break parts of your application into smaller pieces, which is usually a great trade-off to make. Smaller controllers are easier to test, manage, and change. REST also results in the homogenization of your code base and makes it easier for a team to work on together.