Non-Resourceful Custom Routes

A single call to resources in the routes.rb file declares the seven standard routes for your resource. **What if you need additional routes?*

As we saw in the previous lesson, resourceful routes in Rails provide you with a nice set of named routes which are mapped to the common, standardized URL patterns. However, there might be situations when you want to veer off the beaten path and create custom routes in addition to the RESTful ones.

Don't worry, Rails will let you create custom routes that don't fit one of the above seven routes with the member and collection routes. The member route only applies to a single resource, whereas collection will apply to a group of resources.

But first, do you really need them at all?

Before you continue, keep in mind that a better alternative to adding custom, non-resourceful routes is to introduce a new resourceful controller. Not only this keeps your code clean, but gives you better domain model.

Here's what David has to say on this:

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 controllers, it’s been because I’ve had too few of them. I’ve been trying to overload things too heavily.

David Heinemeier Hansson, Full Stack Radio

That said, reality is often messy, and Rails realizes this fact, providing you two ways to add custom, non-resourceful routes for your application.


Member Routes

Let's assume that your users need to see a preview of the course before purchasing it, and you need to expose that route on the courses/:id/preview URL. How should you go about it?

Here's how you define the preview route on the CoursesController using the member block.

resources :courses do
  member do
    get 'preview'
  end
end

The router adds a new route that directs the request to the preview action on the CoursesController class. The remaining routes remain unchanged.

It also passes the course id in params[:id] and creates the preview_course_path and preview_course_url helpers.

preview_course GET    /courses/:id/preview(.:format) courses#preview

If you have a single member route, use the short-hand version by passing the :on option to the route, eliminating the block.

resources :courses do
  get 'preview', on: :member
end

You can go one step further and leave out the :on option.

resources :courses do
  get 'preview'
end

It generates the following route.

➜  bin/rails routes -g preview

         Prefix Verb URI Pattern                         Controller#Action         
course_preview GET  /courses/:course_id/preview(.:format) courses#preview

There are two important differences here:

  1. The course id is available as params[:course_id] instead of params[:id].
  2. The route helpers changes from preview_course_path to course_preview_path and preview_course_url to course_preview_url.

Reminder: Although this approach works fine, a better way is to create a new controller dedicated to previewing images. Typically, you should strive to restrict your controllers with the seven default actions.

Collection Routes

Let's say you want to search all courses and you want to expose it on the courses/search endpoint. You can add a new route for the collection of courses with the collection block.

resources :courses do
  collection do
    get 'search'
  end
end

This adds the following new route. It will also add a search_courses_path and search_courses_url helpers.

search_courses GET    /courses/search(.:format)   courses#search

If you don't need multiple collection routes, just pass :on option to the route.

resources :courses do
  get 'search', on: :collection
end

This will add the same route as above.

Conclusion

Rails allows you to break out of its convention of using seven resourceful routes using the member and collection blocks. Both allow you to define additional routes for your resources than the standard seven routes.

A member block acts on a single member of the resource, whereas a collection operates on a collection of that resource. However, before you use a custom route, make sure if the problem cannot be solved by introducing a new resource.


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.