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.

Nested resources inherit requirements on parent ids

#2694

The problem

Consider the following resource routing code for a site where product ids are of the form ABCD.123:

map.resources :products, :requirements => { :id => /[A-Z]{4}\.[0-9]{3}/ } do |products|
  products.resources :reviews
end

You'd expect product_reviews_path("ABCD.123") to return "/products/ABCD.123/reviews", right? But actually, you get a routing error, because the nested resource (reviews) doesn't know that the product_id parameter is allowed to have periods in it.

What you end up needing is something like:

map.resources :products, :requirements => { :id => /[A-Z]{4}\.[0-9]{3}/ } do |products|
  products.resources :reviews, :requirements => { :product_id => /[A-Z]{4}\.[0-9]{3}/ }
end

...which isn't very DRY.

What this patch does

This patch changes resource generation subtly so that requirements on :id on the parent resource get passed to the child as, for example, requirements[:product_id].

Notes

  1. I'm aware that some people may take issue with using periods in an ID parameter in the first place. I'm not disagreeing, but sometimes these requests come from higher up the chain and aren't easily dismissed ;)
  2. I could perhaps have made this more general, but I'm not aware of any other widely-used parameters that need this kind of treatment. Perhaps this could be a configuration option.
  3. I think cascading the ID requirement in this way is a sensible default, and in any event it can be overridden in the nested resource's requirements. However, if this is the sort of thing that should be off by default, again, I'm happy to create a configuration option.

Thoughts? Questions? Comments? This is my first attempt to patch Rails officially, so I'm completely open to discussion.

Reported by Matt Powell · May 22nd, 2009 @ 03:21 AM

State: resolved
Milestone: 3.0.2
Assigned to: Andrew White Andrew White
Importance: Medium

Activity

  1. David Jones
    David Jones

    +1 this makes a lot of sense. Since the child resource inherits the parent into their URL surely you'd expect the requirements to come across with it.

    May 22nd, 2009 @ 03:54 AM

  2. Jeremy Kemper
    Jeremy Kemper
    • Milestone changed from 2.x to 3.x

    May 4th, 2010 @ 06:48 PM

  3. Andrew White
    Andrew White
    • Assigned user set to Andrew White
    • State changed from new to open
    • Milestone cleared.
    • Importance changed from to Medium

    June 27th, 2010 @ 09:33 PM

  4. Andrew White
    Andrew White

    This almost works with the new router now:

      # Works
      resources :movies, :id => /\d+/ do
        resources :reviews
      end
    
      # Works
      resources :movies, :constraints => { :id => /\d+/ } do
        resources :reviews
      end
    
      # Works
      scope :id => /\d+/ do
        resources :movies do
          resources :reviews
        end
      end
    
      # Fails
      scope :constraints => { :id => /\d+/ } do
        resources :movies do
          resources :reviews
        end
      end
    
      # Fails
      constraints :id => /\d+/ do
        resources :movies do
          resources :reviews
        end
      end
    

    This is because :constraints is a separate hash in the scope. The attached patch merges the constraints from the scope into the resource options.

    June 28th, 2010 @ 12:13 PM

  5. Repository
  6. Anthony Green
    Anthony Green

    I'm still seeing this bug in Actionpack (3.0.0.rc2) but it has been marked as resolved ?

    August 29th, 2010 @ 10:22 PM

  7. Andrew White
    Andrew White

    You are using the new router dsl, right? I'm asking because your example is using the old syntax (and it doesn't work using the old syntax). If you're using the old syntax then nothing will have changed as that's been kept the same deliberately to ease the migration of old apps.

    August 30th, 2010 @ 05:58 AM

  8. Anthony Green
    Anthony Green

    Now I'm not sure if this is the same issue or a slight variation:

    resources :foo, :constraints => { :id => /\d+/ } do
    resources :bar end

    rake routes:

    edit_foo_bar GET    /foo/:foo_id/bar/:id/edit(.:format)          {:id=>/\d+/, :foo_id=>/\d+/, ...}
       foo_bar GET    /foo/:foo_id/bar/:id(.:format)               {:id=>/\d+/, :foo_id=>/\d+/, ...}
       foo_bar PUT    /foo/:foo_id/bar/:id(.:format)               {:id=>/\d+/, :foo_id=>/\d+/, ...}
       foo_bar DELETE /foo/:foo_id/bar/:id(.:format)               {:id=>/\d+/, :foo_id=>/\d+/, ...}
    

    here you can see both :foo_id and :id inherit the constraint. It should only be :foo_id

    August 30th, 2010 @ 09:04 AM

  9. Anthony Green
    Anthony Green

    Moments later I spot the typo:

    the nested resource should be declared singularly so resource not resources

    resources :foo, :constraints => { :id => /\d+/ } do
    resource :bar end

    is there a use case for

    resources :foo do
    resources :bar end

    or should it throw an error ?

    August 30th, 2010 @ 09:11 AM

  10. Jeremy Kemper