#838 √ resolved
S. Brent Faulkner

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

    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

    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

    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]
    end
    

    or

    
    # 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

    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 => true
    

    Personally, 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

    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 => true
    

    Again, 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

    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
      end
    

    or...

    
      map.resources :projects, :has_many => { :milestones => { :features => :tasks } }, :shallow => true
    

    Much 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

    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

    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

    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

    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

    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
  • S. Brent Faulkner

    S. Brent Faulkner August 22nd, 2008 @ 04:06 AM

    patch would not apply... fixed... uploaded new patch.

  • Pratik

    Pratik August 28th, 2008 @ 05:04 PM

    • → Assigned user changed from “” to “Pratik”
  • S. Brent Faulkner

    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

    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"..

Shared Ticket Bins

Attachments