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