Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

add support for shallow nesting of resource routes

#838

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

Reported by S. Brent Faulkner · August 26th, 2008 @ 07:20 AM

State: resolved
Milestone: 2.x
Assigned to: Pratik Pratik
Importance: none

Activity

  1. RSL
    RSL

    Or [and I'm going on a limb here] you could just not make that resource nested so deep. http://weblog.jamisbuck.org/2007...

    August 15th, 2008 @ 01:39 AM

  2. S. Brent Faulkner
    S. Brent Faulkner

    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 ;-)

    August 15th, 2008 @ 01:45 AM

  3. RSL
    RSL

    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. ;)

    August 15th, 2008 @ 02:15 AM

  4. S. Brent Faulkner
    S. Brent Faulkner

    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?

    August 15th, 2008 @ 03:22 AM

  5. S. Brent Faulkner
    S. Brent Faulkner

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

    August 15th, 2008 @ 03:32 AM

  6. S. Brent Faulkner
    S. Brent Faulkner

    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)

    August 16th, 2008 @ 12:52 PM

  7. S. Brent Faulkner
    S. Brent Faulkner
    • Title changed from add support for selective resource routes to add support for shallow nesting of resource routes

    August 16th, 2008 @ 01:13 PM

  8. DHH
    DHH

    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.

    August 16th, 2008 @ 08:45 PM

  9. S. Brent Faulkner
    S. Brent Faulkner

    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
    

    August 17th, 2008 @ 05:14 AM

  10. S. Brent Faulkner
    S. Brent Faulkner

    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

    August 17th, 2008 @ 05:50 AM

  11. S. Brent Faulkner
    S. Brent Faulkner

    tests pass, documentation included, please consider for commit

    (hey, dhh thought it was ok :-)

    August 19th, 2008 @ 03:54 PM

  12. S. Brent Faulkner
  13. S. Brent Faulkner
    S. Brent Faulkner

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

    August 22nd, 2008 @ 04:06 AM

  14. Pratik
    Pratik
    • Assigned user set to Pratik

    August 28th, 2008 @ 05:04 PM

  15. S. Brent Faulkner
    S. Brent Faulkner

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

    August 30th, 2008 @ 05:29 AM

  16. Repository
    Repository
    • State changed from new to resolved

    (from [83c6ba18899a9f797d79726ca0078bdf618ec3d4]) Add support for shallow nesting of routes. [#838 add support for shallow nesting of resource routes 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...

    August 30th, 2008 @ 03:22 PM