Resourceful Routes in Rails

Working with Resourceful Routes in Ruby on Rails

The concept of resourceful routing took me a long time to understand, but once it clicked, it changed how I viewed web applications. This post covers the basics: what a resource is, the routes it generates and how resourceful routing provides a nice organizational structure your Rails applications.

7 min read
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.

💡
A resource R is a temporally varying membership function MR(t), which for time t maps to a set of entities, or values, which are equivalent.

Is your head spinning? 🤯 Mine too. Let's try the other definition he gives in the same section.

💡
A resource is the key abstraction of information. Any information that can be named can be a resource.

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:

  1. Creating a resource (publishing a post, uploading an image)
  2. Reading that resource (viewing the tweets, listening to podcasts)
  3. Updating the resource (changing source code on GitHub, editing the post)
  4. 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.

💡
A resource is an object that you want users to be able to access via URI and perform CRUD operations.

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:

  1. The route to show all profiles is missing. Since we only have a single profile, there's no point in displaying all.
  2. 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.

Define Custom Routes Using the Member and Collection Blocks in Rails
Let’s say you want to add non-resourceful custom routes on your controller. Most often, you’re better off by introducing a new resourceful controller. However, in those cases where you absolutely have to, you can define new routes using the member and collection blocks provided by the Rails router.

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.