Resourceful Routes

Resourceful routing allows you to quickly declare all of the common routes for a given resource.

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 index, show, new, create, edit, update, and destroy actions, 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 makes 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 do 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:

get 'courses'          => 'courses#index'
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.

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.

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.

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. We'll explore the topic of custom routes in the next lesson.

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.

How to Map a Resourceful Route in Rails to Another Controller Class

You can map a resource to a different controller by passing the controller option to the resources method.

resources :posts, controller: "articles"

Without the controller option, the posts resource route will map to the PostsController class.

With the controller option provided, it will map to the ArticlesController class.

$ bin/rails routes -g posts

   Prefix Verb   URI Pattern               Controller#Action
    posts GET    /posts(.:format)          articles#index
          POST   /posts(.:format)          articles#create
 new_post GET    /posts/new(.:format)      articles#new
edit_post GET    /posts/:id/edit(.:format) articles#edit
     post GET    /posts/:id(.:format)      articles#show
          PATCH  /posts/:id(.:format)      articles#update
          PUT    /posts/:id(.:format)      articles#update
          DELETE /posts/:id(.:format)      articles#destroy

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.

As we saw, the Rails router is very powerful and highly flexible. Routing is also a two-way street: match URLs to controller actions, and generate URLs dynamically. Additionally, the router uses powerful conventions such as resourceful routing to automatically generate sensible routes for you.


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. You can also subscribe to my blog to receive future posts via email.