add support for shallow nesting of resource routes
Reported by S. Brent Faulkner | August 15th, 2008 @ 01:23 AM | in 2.x
UPDATED: I've edited this post to reflect the current patch... the original post is still included after this for the record.
Attached is a patch for resource mapping (and appropriate tests) that implements (optional) shallow nesting for resources. This allows you to specify :shallow => true for a resource. The option is inherited by any nested resources and causes any member routes (those with an id parameter) to not require the parent path prefix.
So, you can now...
map.resources :projects, :shallow => true do |project|
project.resources :milestones do |milestone|
milestone.resources :features do |feature|
feature.resources :tasks
end
end
end
In addition, the patch enhances the has_many shorthand notation to allow use of a hash to specify multiple levels of nested resources, which can be combined with the :shallow option as follows...
map.resources :projects, :has_many => { :milestones => { :features => :tasks } }, :shallow => true
vvvvv original post vvvv
The attached patch adds support for specifying a subset of named routes to create when mapping verb-oriented controllers for collections of resources.
You can specify which routes to add using :only and/or :except options with array values containing symbols of :collection, :new, :member and :associations.
This is especially useful for creating nested routes, so that the redundant parent ids are not required for routes containing explicit ids.
For example...
map.resources :posts do |post|
post.resources :comments, :only => [ :collection, :new ]
end
map.resources :comments, :except => [ :collection, :new ]
The generated routes would nest the collection and new record routes for comment resources within posts...
e.g. GET /posts/1/comments or POST /posts/1/comments
However, the routes with explicit ids would not require the post_id...
e.g. GET /comments/2 instead of GET /posts/1/comments/2
This really helps to clean up urls when nesting resources more than once.
For example, instead of:
map.resources :projects do |project|
project.resources :milestones do |milestone|
milestone.resources :features do |feature|
feature.resources :tasks
end
end
end
You could use:
map.resources :projects do |project|
project.resources :milestones, :only => [ :collection, :new ]
end
map.resources :milestones, :except => [ :collection, :new ] do |milestone|
milestone.resources :features, :only => [ :collection, :new ]
end
map.resources :features, :except => [ :collection, :new ] do |feature|
feature.resources :tasks, :only =>[ :collection, :new ]
end
map.resouces :tasks, :except => [ :collection, :new ]
This would result in the ability to simply access:
/features/72/tasks
Instead of:
/projects/13/milestones/16/features/72/tasks
Comments and changes to this ticket
-
RSL August 15th, 2008 @ 01:39 AM
Or [and I'm going on a limb here] you could just not make that resource nested so deep. http://weblog.jamisbuck.org/2007...
-
S. Brent Faulkner August 15th, 2008 @ 01:45 AM
Actually that is the purpose of the selective routes.
Jamis's proposed solution to the problem winds up doubly mapping the routes... so there is one route for /projects/1/milestones/2 and a second for /milestones/2
By selectively mapping them, the show_milestones route exists... the show_project_milestones routes does not.
(and my example was an exageration on purpose ;-)
-
RSL August 15th, 2008 @ 02:15 AM
I admit I was blinded by that last URL. If the goal of this is to DRY up the routing there while encouraging a more direct routing where possible, I'd like to see something more akin to
map.resources :users do |user| # Yes, this is rather hideous. user.resources :comments, :nest => [:collection, :new], :flat => [:member] endor
# A little better. ;) map.resources :users, :nest => {:comments => [:collection, :new]Apologies for my completely kneejerk reaction to seeing "nested resource" and that last URL. ;)
-
S. Brent Faulkner August 15th, 2008 @ 03:22 AM
How about using a notation similar to that used for ActiveRecord#find :includes
We could then use...
map.resources :projects => { :milestones => { :features => :tasks } }Instead of the deeply nested blocks of my previous example.
Then, to get the flattening, we could just add a boolean option...
map.resources :projects => { :milestones => { :features => :tasks } }, :flatten => truePersonally, I wouldn't mind the :flatten option defaulting to true and requiring :flatten => false to get the "legacy" style of routes.
Thoughts?
-
S. Brent Faulkner August 15th, 2008 @ 03:32 AM
Hmmm... just remembered the addition of has_many and has_one...
So, I guess I'm suggesting the following...
map.resources :projects, :has_many => { :milestones => { :features => :tasks } }, :flatten => trueAgain, I guess the default could be to flatten, but this would mean that we're changing an existing behaviour for the simple case...
map.resources :posts, :has_many => :comments(which I don't think is a good idea -- ie. changing the existing behaviour -- so, I can live with needing to explicitly specify :flatten => true :-)
I think I'll try and build up a patch for this version instead...
-
S. Brent Faulkner August 16th, 2008 @ 12:52 PM
OK... how about this? I've attached a new patch for resource mapping (and appropriate tests) that implements (optional) shallow nesting for resources.
This allows you to specify :shallow => true for a resource. The option is inherited by any nested resources and causes any member routes (those with an id parameter) to not require the parent path prefix.
So, you can now...
map.resources :projects, :shallow => true do |project| project.resources :milestones do |milestone| milestone.resources :features do |feature| feature.resources :tasks end end endor...
map.resources :projects, :has_many => { :milestones => { :features => :tasks } }, :shallow => trueMuch nicer than my original proposal.
In either case, I'd be able to access resources with paths like the following...
/projects /projects/1 /projects/1/milestones /milestones/2 /milestones/2/features /features/56/tasks/new
(no redundant parent_id parameters... just what's required)
-
S. Brent Faulkner August 16th, 2008 @ 01:13 PM
- → Title changed from add support for selective resource routes to add support for shallow nesting of resource routes
-
DHH August 16th, 2008 @ 08:45 PM
Really nice. Shallow is a great term for it too. It needs documentation, though. Then I'd say it would be ready to go. Nice work.
-
S. Brent Faulkner August 17th, 2008 @ 05:14 AM
Thanks again, David.
I've attached a new patch with:
a) documentation
b) the support for passing deep :has_many information using a hash/array
for example:
map.resources :projects, :has_many => { :milestones => { :features => :tasks } }, :shallow => true -
S. Brent Faulkner August 17th, 2008 @ 05:50 AM
regarding (b) ... I had actually thought that this was already supported, but must have been thinking of the ActiveRecord finder :include option, so added it in since that was was part of the beauty of this solution
-
S. Brent Faulkner August 19th, 2008 @ 03:54 PM
tests pass, documentation included, please consider for commit
(hey, dhh thought it was ok :-)
-
-
S. Brent Faulkner August 22nd, 2008 @ 04:06 AM
patch would not apply... fixed... uploaded new patch.
-
Pratik August 28th, 2008 @ 05:04 PM
- → Assigned user changed from to Pratik
-
S. Brent Faulkner August 30th, 2008 @ 05:29 AM
I saw a recent commit to resources.rb and tested the patch. It wouldn't apply again, so I've rebased and attached the new one...
-

Repository August 30th, 2008 @ 03:22 PM
- → State changed from new to resolved
(from [83c6ba18899a9f797d79726ca0078bdf618ec3d4]) Add support for shallow nesting of routes. [#838 state:resolved]
Adds :shallow option to resource route definition. If true, paths for nested resources which reference a specific member (ie. those with an :id parameter) will not use the parent path prefix or name prefix.
Example :
map.resources :users, :shallow => true do |user| user.resources :posts end
-
GET /users/1/posts (maps to PostsController#index action as usual) named route "user_posts" is added as usual.
-
GET /posts/2 (maps to PostsController#show action as if it were not nested) Additionally, named route "post" is added too. http://github.com/rails/rails/co...
Please Login or create a free account to add a new comment.
You can update this ticket by sending an email to from your email client. (help)
Create your profile
Help contribute to this project by taking a few moments to create your personal profile. Create your profile »
Source available from github
Repository is at http://github.com/rails/rails
Check out the development master (Edge Rails):
git clone git://github.com/rails/rails.git
Creating or reviewing a patch
See the contributor guide.
Creating a feature request
Please don't. If you want a new feature in Rails, you'll have to pull up your sleeves and get busy yourself. Or convince someone else to do it. See the contributor guide on how to get going. But posting them here is just going to lead to ticket root.
Creating a bug report
When creating a bug report, be sure to include as much relevant information as possible. Post the code sample that causes the problem. Preferably, alter the unit tests and show through either changed or added tests how the expected behavior is not occuring.
Security vulnerabilities should be reported via an email to security@rubyonrails.org, do not use trac for reporting security vulnerabilities. All content in trac is publicly available as soon as it is posted.
Then don't get your hopes up. Unless you have a "Code Red, Mission Critical, The World is Coming to an End" kinda bug, you're creating this ticket in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the ticket automatically will see any activity or that others will jump to fix it. Creating a ticket like this is mostly to help yourself start on the path of fixing the problem and for others to sign on to with a "I'm having this problem too"..
