What I've come to embrace is that being almost fundamentalistic about when I create a new controller to stay adherent to REST has served me better every single time.
Every single time I've regretted the state of my controller, it's been because I've had too few of them. I've been trying to overload things too heavily.
- David Heinemeier Hansson, on Full Stack Radio
The concept of resources is very powerful in Rails. With a single call to a method named resources
, Rails will generate seven different routes for you, saving you a lot of typing.
But saving a few keystrokes is just the cherry on top. The biggest benefit of using resourceful routing is that it provides a nice organizational structure for your Rails application, and helps you figure out how to name and organize the controller classes and action methods.
Not only that, resourceful routing imposes certain constraints on your project which make most Rails applications consistent and familiar. When a developer says that they have a post resource, another can safely assume (most of the time) that they have a PostController
class with actions like create, show, edit, new, ...
along with a Post
model saved in the posts
table in the database. This conceptual compression is not to be taken lightly.
Since the concepts of REST and Resource are such an integral part of all Rails applications, let's try to understand them in depth.
What's a Resource?
A resource, like an object, is one of those concepts that's difficult to put into words. You intuitively understand them, but you can't express them. I'll stick to how Roy Fielding, the inventor of REST, defined them in his dissertation.
Is your head spinning? 🤯 Mine too. Let's try the other definition he gives in the same section.
Much better, right? 💡 I think that definition captures the essence of resources really well.
Any concept in your application domain can be a resource: an article, a course, or an image. It doesn't have to be a physical entity, but can be an abstract concept such as a weather forecast or an SSH connection to connect to a remote server.
The main point is: you have total freedom in choosing whatever idea you are using in your application as a resource.
How to Resources Relate to REST?
Now, you may have heard the term REST (Representational State Transfer). In simple terms, REST is a way for different machines on the Internet to communicate with each other using resources.
It's important to remember that you only get a representation of the resource (hence the name 'Representational State Transfer'). For example, if you ask the HTML representation of a Post resource (like Hotwire or traditional web apps), you'll get the post as an HTML. If you ask for a JSON representation (like many SPA frameworks do), you'll get the post JSON. Makes sense?
But Why do I need a Resource?
Good question. You might be wondering what this discussion of resources has to do with routing. Well, resources are fundamental to routing. They allow you to reduce seven different routes into a single method call. Let's learn how.
Identifying Patterns
If you've used any software application, you must have noticed that most of them have a few common patterns. Most of the time, you're:
- Creating a resource (publishing a post, uploading an image)
- Reading that resource (viewing the tweets, listening to podcasts)
- Updating the resource (changing source code on GitHub, editing the post)
- Deleting the resource (removing a folder from Dropbox, deleting your profile picture)
No matter how fancy your application is, I guarantee it will have some form of these four actions that your users can take. Collectively, they're called CRUD which stands for Create, Read, Update, and Delete.
For each of these actions, your application should have a route. Assuming you're building a course platform, your routes file might have the following routes.
post 'courses' => 'courses#create'
get 'course' => 'course#show'
patch 'courses/:id' => 'courses#update'
delete 'courses/:id' => 'courses#destroy'
In addition to these four routes, you'll typically have three more:
- one route to see all the courses
get 'courses' => 'courses#index'
- two routes to fetch the pages that allow the users create and modify the courses. These are different from the routes that actually save and update the courses.
get 'courses/new' => 'courses#new'
get 'courses/:id/edit' => 'courses#edit'
So we have a total of seven routes that are common to most resources in most web applications.
- If you are building a blog, you'll have these seven routes for posts.
- If you're building Instagram, you'll have the same routes for images.
- If you're building a course platform, you'll have these routes for courses and lessons.
No matter the topic, you'll most likely have some subset of these seven routes for each resource.
Since this pattern is so common and repetitive, Rails introduced the concept of resourceful routing, whereby calling one method named resources
and passing the plural name of the resource, i.e. :posts
, :courses
, :images
, etc. you get these seven routes for free. Rails will automatically generate them for you.
Rails.application.routes.draw do
resources :courses
resources :photos
resources :posts
end
In addition, resourceful routing also generates sensible names for these routes. For example, you'll have names like course_path
, edit_course_path
at your disposal without providing the :as
option.
The following table summarizes what you get with a single call to resources :courses
.
➜ bin/rails routes -g course
Prefix Verb URI Pattern Controller#Action
courses GET /courses(.:format) courses#index
POST /courses(.:format) courses#create
new_course GET /courses/new(.:format) courses#new
edit_course GET /courses/:id/edit(.:format) courses#edit
course GET /courses/:id(.:format) courses#show
PATCH /courses/:id(.:format) courses#update
PUT /courses/:id(.:format) courses#update
DELETE /courses/:id(.:format) courses#destroy
The values under the Prefix column represent the prefix of the route's name, e.g. new_course
gives you new_course_path
and new_course_url
helpers, and so on. The rows with empty prefixes just follow the ones from the above row.
In addition to having multiple resources, there's also a singular form of resourceful routes. It represents a resource that only has one, single form. For example, a logged-in user's profile. You can use the resource
method for this.
resource :profile
It creates the following routes:
new_profile GET /profile/new(.:format) profiles#new
edit_profile GET /profile/edit(.:format) profiles#edit
profile GET /profile(.:format) profiles#show
PATCH /profile(.:format) profiles#update
PUT /profile(.:format) profiles#update
DELETE /profile(.:format) profiles#destroy
POST /profile(.:format) profiles#create
Note two important differences from the plural version:
- The route to show all profiles is missing. Since we only have a single profile, there's no point in displaying all.
- None of the routes contain an
id
segment key. We don't need it to identify a profile as there's only one profile.
What if I don't have all seven routes?
Sometimes, you don't want all seven routes that resources
method creates for you. In such cases, you can pass the only
or except
options to filter the ones you need or don't need.
resources :courses, only: [:index, :show]
resources :courses, except: [:delete]
You can even nest multiple resources, but I'll leave that topic for a different post.
Can I define more routes in addition to the seven resourceful routes?
Yes, Rails provides you the member
and collection
helpers to define non-conventional routes. Check out the following post to learn more.
That said, any time you find yourself reaching for non-resourceful routes, stop and consider if you need a separate resource with a dedicated controller instead.
Conclusion
To summarize what we've learned so far, any concept in your application domain can be a resource: an article, a course, or an image, even a user session. It doesn't have to be a physical entity, but anything that can be named.
Resourceful routing allows you to quickly declare all of the common routes for a given resource. A single call to resources
declares seven routes for index
, show
, new
, create
, edit
, update
, and destroy
actions.
Using resourceful routing not only structures your Rails codebase but also provides a nice organizational structure, answering the common questions: what to name this controller or method. So put it to good use, and only resort to non-resourceful routes when you have to.
That's a wrap. I hope you found this article helpful and you learned something new.
As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I reply to all emails I get from developers, and I look forward to hearing from you.
If you'd like to receive future articles directly in your email, please subscribe to my blog. Your email is respected, never shared, rented, sold or spammed. If you're already a subscriber, thank you.