Skip to Main Content

TWIL 2022-06-24

Featured image for TWIL blog post on Rails Routing with a focus on Shallow Nested Resources - insights for Ruby and Rails developers.

Dive into the latest installment of TWIL, where we serve up crisp micro-lessons for the software development enthusiast. This week, Katie gives us a lesson on Rails Routing: Shallow Nested Resources. Grasp the elegance of Rails' ability to simplify resourceful routing, trimming unnecessary nested path segments for a cleaner and more efficient coding experience in Ruby on Rails. Join us and fortify your coding toolbox with these insightful tidbits from the field.

Rails Routing: Shallow Nested Resources

Normally, when you’re creating nested resource routes in Rails, you’d set them up like:

resources :posts do
  resources :comments
end

The resulting routes would be:

post_comments     GET       /posts/:post_id/comments(.:format)
post_comments     POST      /posts/:post_id/comments(.:format)
new_post_comment  GET       /posts/:post_id/comments/new(.:format)
edit_post_comment GET       /posts/:post_id/comments/:id/edit(.:format)
post_comment      GET       /posts/:post_id/comments/:id(.:format)
post_comment      PATCH/PUT /posts/:post_id/comments/:id(.:format)
post_comment      DELETE    /posts/:post_id/comments/:id(.:format)

This is fine, but once you have the record’s id, you very likely won’t need the id of its parent resource as well (you could as easily find the record by its own id and retrieve its parent’s id from it directly). This also causes ballooning of path and URL helper names and argument lists, e.g.,

post_comment_path(comment.post_id, comment.id) vs comment_path(comment_id).

This becomes particularly onerous for deeper nesting levels, e.g., something like:

author_post_comment_path(comment.post.author_id, comment.post_id, comment.id)

where all the identifying keys are available via the comment itself, and thus just the comment id.

To set up those routes that should only need the record’s id without parent nesting, you’d need to do:

resources :posts do
  resources :comments, except: [:show, :edit, :update, :destroy]
end
resources :comments, only: [:show, :edit, :update, :destroy]

Resulting routes/names:

post_comments    GET       /posts/:post_id/comments(.:format)
post_comments    POST      /posts/:post_id/comments(.:format)
new_post_comment GET       /posts/:post_id/comments/new(.:format)
edit_comment     GET       /comments/:id/edit(.:format)
comment          GET       /comments/:id(.:format)
comment          PATCH/PUT /comments/:id(.:format)
comment          DELETE    /comments/:id(.:format)

This is a lot of extra… extra, especially for a repeated pattern and especially especially with deeper nesting.

A Better Way

…which, of course, Rails has had since at least Rails 2, but which I somehow never, ever knew.

The :shallow option for resources does exactly what you’d want here.

resources :posts, shallow: true do
  resources :comments
end

Resulting routes/names:

post_comments    GET       /posts/:post_id/comments(.:format)
post_comments    POST      /posts/:post_id/comments(.:format)
new_post_comment GET       /posts/:post_id/comments/new(.:format)
edit_comment     GET       /comments/:id/edit(.:format)
comment          GET       /comments/:id(.:format)
comment          PATCH/PUT /comments/:id(.:format)
comment          DELETE    /comments/:id(.:format)

This works for deeper nesting levels as well and can be overridden per child resource by setting shallow: false if desired.

Resources

  • Rails
Katie Linero's profile picture
Katie Linero

Senior Software Engineer

Related Posts