Write Software, Well
Understanding Shallow Nested Routes in Rails

August 06, 2024

Understanding Shallow Nested Routes in Rails

This post is part of my handbook on the Rails Router.

In the previous post, we learned how nested resources work. Consider the following route configuration:

# config/routes.rb
resources :courses do
resources :lessons
end

It generates the following routes:

$ bin/rails routes -g lesson
Prefix Verb URI Pattern Controller#Action
course_lessons GET /courses/:course_id/lessons(.:format) lessons#index
POST /courses/:course_id/lessons(.:format) lessons#create
new_course_lesson GET /courses/:course_id/lessons/new(.:format) lessons#new
edit_course_lesson GET /courses/:course_id/lessons/:id/edit(.:format) lessons#edit
course_lesson GET /courses/:course_id/lessons/:id(.:format) lessons#show
PATCH /courses/:course_id/lessons/:id(.:format) lessons#update
PUT /courses/:course_id/lessons/:id(.:format) lessons#update
DELETE /courses/:course_id/lessons/:id(.:format) lessons#destroy

You must have noticed that the showeditupdate, and destroy routes for a lesson don't really need the course. They can exist on their own.

  • show: Given a lesson id, show this lesson
  • edit: Given a lesson id, show the form to edit this lesson
  • update: Given a lesson id, update this lesson
  • destroy: Given a lesson id, delete this lesson

In contrast, the indexcreate, and new routes for a lesson will always need a course, otherwise Rails won't know which course we're talking about.

  • index: For which course we should show all the lessons?
  • create: Which course we should add this lesson to?
  • new: For which course we should add new lesson?

To address this, we could define our nested routes like this:

resources :courses do
resources :lessons, only: [:index, :new, :create]
end

resources :lessons, only: [:show, :edit, :update, :destroy]

However, Rails provides the shallow option as a shortcut.

resources :courses do
resources :lessons, shallow: true
end

Shallow nesting strikes a balance between descriptive routes and deep nesting.

Let's review the generated routes.

$ bin/rails routes -g lesson
Prefix Verb URI Pattern Controller#Action
## Need a Course
course_lessons GET /courses/:course_id/lessons(.:format) lessons#index
POST /courses/:course_id/lessons(.:format) lessons#create
new_course_lesson GET /courses/:course_id/lessons/new(.:format) lessons#new
## Doesn't Need a Course
edit_lesson GET /lessons/:id/edit(.:format) lessons#edit
lesson GET /lessons/:id(.:format) lessons#show
PATCH /lessons/:id(.:format) lessons#update
PUT /lessons/:id(.:format) lessons#update
DELETE /lessons/:id(.:format) lessons#destroy

If you have multiple resources nested under a single parent resource, you can pass the shallow option to the parent.

resources :courses, shallow: true do
resources :lessons
resources :students
end

Shallow nesting lets you avoid deep nesting by generating the collection actions scoped under the parent, so as to get a sense of the hierarchy, but by not nesting the member actions. In other words, it only builds routes with the minimal amount of information to uniquely identify the resource.

Subscribe to get future posts via email (or grab the RSS feed)

Comments

No comments yet. Be the first to leave one.

Sign in to leave a comment.