Rails’ routing mechanism is pretty slick. In a very intuitive way you’re able to describe the resources you want exposed at the URL level with this routing-DSL:
1 2 3 4 5 |
map.resources :users do |user| user.resources :articles do |article| article.resources :comments end end |
However, while this configuration makes an article available at /users/1/articles/1 and comments at /users/1/articles/1/comments/1 there are often times when you want to bypass the full nested hierarchy and directly access the resource in question. Now, with the shallow route option, you can.
1 2 3 4 5 |
map.resources :users, :shallow => true do |user| user.resources :articles do |article| article.resources :comments end end |
This configuration removes all nested member paths (like /articles/1/comments/1) and makes them directly accessible (as /comments/1). Basically, routes are built only with the minimal amount of information that’s needed to uniquely identify the resource.
1 2 3 4 |
user_articles_path(1) #=> '/users/1/articles' article_path(2) #=> '/articles/2' article_comments_path(3) #=> '/articles/3/comments' comment_path(4) #=> '/comments/4' |
No longer do you need to wrestle with infinitely nested routes, the :shallow option automatically makes all routes as concise as possible.
tags: ruby, rubyonrails

This is so useful I’d rather have it be the default.
I think you have a typo in line 1 of the second code snippet: should be :shallow, not :shallpow.
Hey, cool feature. By the way, I think you got a typo there: map.resources :users, :shallpow => true do |user| # should be :shallow, right?
map.resources :users, :shallpow, huh?
sounds like a promise that your code will explode :p
PS. Great feature, thanks for sharing!
I look forward to these posts. I assume you meant :shallow => true, and not :shallpow..
In the second code example you put ”:shallpow => true, you meant => true”
Thanks for these posts. Nice to keep up without having to review all the commits. :)
Great post and great features Thanks Ryan
I think there is just a little typo with shallpow instead of shallow :)
There are a bunch of typos in your second code example.
Type-o:
I’m guessing “map.resources :users, :shallpow => true do |user|” should be “map.resources :users, :shallow => true do |user|”
Otherwise, cool feature. I think it will be both handy to those comfortable with rails routes, and a little bit more confusing for those not.
It seems you have a typo in your second code block (:shallpow instead of :shallow).
Really interresting, as usual. Although, It seems there is some typos in code examples.
“shallow” in second code block has accidentally changed into “shallpow” ;)
Rails 2.2 are going to kick some serious butt. Keep up the good work Ryan, there’re lot of people getting edge rails info from your blog.
In the second routing example, :shallpow should :shallow. Otherwise, great post! Thanks Ryan!
Looks like your fingers have been hitting several buttons: Line 3 on both examples: :resourcs should be :resources, and :shallpow should be :shallow, I think?
Nice article, thanks
Steve
Typo on line 3 in each of your first two examples: articles.resourcs.
Also, thanks!
TODO ”:shallpow => true => => true” – in 2nd code block
This is very cool and should make some of my routes files much shorter.
Think you must have hit ‘p’ when you typed :shallow => true
Just a little typo: you have written ”: shallpow” in your code snippet.
Another great tip Ryan! Just to let you know, there is a typo in your second code block.
It’s great. Please, correct a little errata in:
article.resourcs :comments
Should be:
article.resources :comments
typo s/shallpow/shallow in second snippet
thanks for the write up :)
map.resources :users, :shallpow => true do |user| line 1: should be w ?
I may be missing something here, but…
Aren’t you going to have to do a bunch of funny business in the controllers to discern if the request is coming from a shallow or full route?
In cases like this, I’ve been doing something like this:user_articles_controller.rb assumes that user_id will be defined in params while articles_controller.rb does not. Its a pain to have two controllers, but I think that’s better than having a bunch of logic in the controller to discern what to load.
So I don’t quite see how the “shallow” option helps since I need to specify different controllers for the routes.
Or…am I doing something the hard way?
Nice, but what about content duplication that play negatively with search engines?
Also, it will not play nice if the nested resource is expecting something from parent one, which sometimes is the objective of nested resources in the first place :-)
(or I’m missing something?)
There are a couple of typos in the first two code samples. “resourcs” and “shallpow”.
This sounds like a nice shortcut to route configuration.
shallpow typo
That second code block has a typo on the first line, methinks. (“shallpow”)
- articles.resourcs :comments + articles.resources :comments
Sorry to be so pedantic!
I think “shallpow” is supposed to be “shallow”.
Great idea! Ryan, just a little type in your code :shallow => true instead of :shallpow => true Thanks, Peter.
:shallow => true
not
:shallpow => true
Typo: :shallpow -> :shallow
Ryan, there is a small typo Second example, 1 line should be :shallow instead of :shallpow.
Sorry about that guys – my akismet config was broken meaning that all your comments were dumped in some sort of pergatorial state. Thanks for the typo fixes – I think I got the message :)
@ Scoty: you’ve been doing things the hard way!
The idea is that both the nested routes and the top-level routes go to the same controller.
In that controller, what I always do, is try to determine the top-level model based on the params. I put this in a before_filter:
@user = User.find params[:user_id]
When it’s nil I know I’m in the top-level route, when it’s set I’m in the nested route.
@joost: that makes sense, but…
Your redirect_to’s are going to be different depending on the nesting status (articles_path vs. user_articles_path(@user), etc.) Do you put a bunch of if statements to account of the differences?
And what about the views? All the links and form targets are going to vary depending on whether your working with a nested route or not.
It seems messy to have a bunch of conditions in your controllers and views.
Or is there a more graceful approach that I am not seeing?
I don’t think shallow routes option is a good thing. It will make an additional redundant entry url for the article pages. is it a good thing to have two url refer ti the same page.
excuse me sir but i think there might be a typo!
I think that with shallow set to true no map.resources :articles etc. should be needed, so please correct me if I’m wrong… but it seems that no formatted_ path pendants are created so the following does not work if I need e.g. formatted_articles_path somewhere.
map.resources :artist_profiles, :shallow => true do |artist| artist.resources :articles do |article| article.resources :comments end artist.resources :releases artist.resources :events do |event| event.resources :member_photo_albums end artist.resource :playlist artist.resource :photo_album end
I’ve always built this concept of nested resources still being available as first-order resources as well. However, as @Scoty points out there are issues with funniness in the controller doing detection on how the resource is being viewed, as the first-order resource, or the nested one.
I would be interested in best practices about how to handle this in elegant ways – and not just assuming it’s always a nested resource of users, but that many resources can have other nested resources, and can vary.
Nice work Ryan. I have a question though. Pretend you need to want to page and sort those results resfully: ’/users/page/1/by/name’ ‘users/1/articles/page/1/by/date’ ‘users/1/articles/28/comments/page/1/by/date’
Is there a way to apply those to any level of the hirarchy?
You morons! How many times you want to point one stupid typo?! RTCF – Read The Comments First!
Hi Ryan,
These routes are not generated :
articles_path #=> ’/articles’ comments_path #=> ’/comments’
This is probably a bug.
Thanks.
As the author of the feature, I’m here to clear up a bit of confusion…
When you use the shallow option, it does not generate both routes… any routes that reference an explicit id will not require the parent id, but any routes that do not will.
For example, given the above example of users/articles/comments…
The path for all user would be: /users The path for a specific user would be: /users/1
The path for all articles for a user would be: /users/1/articles The path for a specific article would be: /articles/2 This is instead of /users/1/articles/2 (which is what it would be without the shallow option)
Similarly…
The path for all comments for a given article would be: /articles/2/comments The path for a specific comment would be: /comments/3
If you want a route for ALL comments (or ALL articles) you’d need to explicitly map it.
One other enhancement that goes along with this is the :has_many option has been extended to support nesting in a similar manner to the :include option in ActiveRecord finds.
e.g. map.resources :users, :shallow => true, :has_many => { :articles => :comments }
cheers.
Thanks for the clarification, Brent – I’ve cleaned up the post.
Now, Now, gentlemen. There’s no point in getting so angry over a simple mistake, now is there? It’s fixed now so let’s all be thankful for no more bickering, okay? Great save there Ryan!!!
Hi All,
i want to create a question. As per your eg …
user_articles_path(1) #=> ’/users/1/articles’ article_path(2) #=> ’/articles/2’ article_comments_path(3) #=> ’/articles/3/comments’ comment_path(4) #=> ’/comments/4’
Is it right? user_articles_comments_path(3) #=> ’/users/3/articles/3/comments/3’
If i want a relation like this then how can we create it? ’/users/5/articles/8/comments/12’ ??
Thanks in advance… Vipin