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.

Routing breakage in 3.0.3

#6028

Moving from 3.0.1 to 3.0.3 caused 647 spec failures for me in my app, all due to "No route matches" exceptions being raised.

Here's an example failure, triggered by calling issues_path:

No route matches {:controller=>"issues"}

Looking at the output of rake routes, I can see that the route really isn't there.

Here's the route in question:

resources :issues do
  collection do
    get 'page/:page' => 'issues#index', :page => %r{\d+}
  end
end

There are actually more routes declared inside the resource and inside the collection block, but they aren't necessary to reproduce the problem; only the get route is required to trigger the problem.

The route gives us paginated issue paths like /issues/page/3 and so on, which I am using rather than query strings (/issues?page=3) so that I can do page caching on the index.

git bisect reveals that commit 1a2b28c9d9de is the culprit:

commit 1a2b28c9d9deb7958124917195529f1b96b6bd82 (refs/bisect/bad)
Author: José Valim <jose.valim@gmail.com>
Date:   Wed Sep 29 14:24:32 2010 +0200

    Ensure that named routes do not overwrite previously defined routes.

Reported by Wincent Colaiuta · November 21st, 2010 @ 08:54 PM

State: resolved
Milestone: none
Importance: Medium

Activity

  1. Wincent Colaiuta
    Wincent Colaiuta

    One minor correction to what I wrote above. The route is actually visible in the rake routes output:

        issues GET    /issues/page/:page(.:format)                              {:action=>"index", :controller=>"issues", :page=>/\d+/}
               GET    /issues(.:format)                                         {:action=>"index", :controller=>"issues"}
               POST   /issues(.:format)                                         {:action=>"create", :controller=>"issues"}
     new_issue GET    /issues/new(.:format)                                     {:action=>"new", :controller=>"issues"}
    edit_issue GET    /issues/:id/edit(.:format)                                {:action=>"edit", :controller=>"issues"}
         issue GET    /issues/:id(.:format)                                     {:action=>"show", :controller=>"issues"}
               PUT    /issues/:id(.:format)                                     {:action=>"update", :controller=>"issues"}
               DELETE /issues/:id(.:format)                                     {:action=>"destroy", :controller=>"issues"}
    

    Here's a quick test demo in Rails console of how this blows up:

    >> include Rails.application.routes.url_helpers
    => Object
    >> issues_path
    ActionController::RoutingError: No route matches {:controller=>"issues"}
    

    November 21st, 2010 @ 09:10 PM

  2. Wincent Colaiuta
    Wincent Colaiuta

    For comparison, the output of rake routes under Rails v3.0.1:

        issues GET    /issues/page/:page(.:format)                              {:page=>/\d+/, :controller=>"issues", :action=>"index"}
        issues GET    /issues(.:format)                                         {:controller=>"issues", :action=>"index"}
        issues POST   /issues(.:format)                                         {:controller=>"issues", :action=>"create"}
     new_issue GET    /issues/new(.:format)                                     {:controller=>"issues", :action=>"new"}
    edit_issue GET    /issues/:id/edit(.:format)                                {:controller=>"issues", :action=>"edit"}
         issue GET    /issues/:id(.:format)                                     {:controller=>"issues", :action=>"show"}
         issue PUT    /issues/:id(.:format)                                     {:controller=>"issues", :action=>"update"}
         issue DELETE /issues/:id(.:format)                                     {:controller=>"issues", :action=>"destroy"}
    

    November 21st, 2010 @ 09:13 PM

  3. Andrés Mejía
    Andrés Mejía

    I think this is the intended behavior, and not a bug:

    When you say

    get 'page/:page' => 'issues#index', :page => %r{\d+}
    
    you are saying that a page parameter is required. If you pass the page parameter it works as expected:
    ruby-1.9.2-p0 > issues_path(:page => 666)
     => "/issues/page/666"
    

    If you want the page parameter to be optional, do

    get '(page/:page)' => 'issues#index', :page => %r{\d+}
    

    This makes it work as you want:

    ruby-1.9.2-p0 > issues_path
     => "/issues" 
    ruby-1.9.2-p0 > issues_path(:page => 1)
     => "/issues/page/1" 
    ruby-1.9.2-p0 > issues_path(:page => 666)
     => "/issues/page/666"
    

    Maybe the bug was on Rails 3.0.1 that didn't enforce the presence of the page parameter?

    November 22nd, 2010 @ 11:47 AM

  4. Wincent Colaiuta
    Wincent Colaiuta

    Thanks, I'll apply that as a workaround for now.

    As for whether it's intended or not, I don't know. It would be good to have clarification from one of the people who designed the router.

    I always thought the basic idea in the routing file was that rules would have a kind of top-down precedence, so whatever gets declared first and can match, wins. So declaring the "page" rule after the non-paged case was consistent with that idea:

    ie. the non-paged case would be the default which would usually apply, but in the presence of a page parameter the more specific paged case would apply

    November 22nd, 2010 @ 11:56 AM

  5. Andrés Mejía
    Andrés Mejía

    By the way, it's not working properly for me on Rails 3.0.1, either:

    On routes.rb :

      resources :issues do
        collection do
          get 'page/:page' => 'issues#index', :page => %r{\d+}
        end
      end
    

    This is what I get:

    $ rails c
    Loading development environment (Rails 3.0.1)
    ruby-1.9.2-p0 > include Rails.application.routes.url_helpers
     => Object 
    ruby-1.9.2-p0 > issues_path
     => "/issues" 
    ruby-1.9.2-p0 > issues_path(:page => 666)
     => "/issues?page=666"
    

    So as you see, the it didn't use the page/:page route.

    Do you get the same?

    November 22nd, 2010 @ 12:27 PM

  6. Wincent Colaiuta
    Wincent Colaiuta

    I do, but I don't generate the page links using the helper in my app; I have an lib module that does that (via string concatenation, gasp)...

    November 22nd, 2010 @ 02:24 PM

  7. Andrés Mejía
    Andrés Mejía

    All right. I think there's nothing to fix here. Can you change this ticket's state to "invalid"?

    November 22nd, 2010 @ 02:32 PM

  8. Wincent Colaiuta
    Wincent Colaiuta

    Don't think I'm the one to do that, as I'm not sure that it is invalid. Better for someone from the core team, or someone more heavily involved in the router design, to make that decision.

    November 23rd, 2010 @ 08:03 AM

  9. Prem Sichanugrist (sikachu)
    Prem Sichanugrist (sikachu)
    • State changed from new to open
    • Assigned user set to Prem Sichanugrist (sikachu)
    • Importance changed from to Low

    Hi,

    I can confirm that the behavior has been changed. That commits were making sure that no duplicate named route should be created. That's why in 3.0.3 you would see issues only appearing on the first line.

    However, I think there's a bug here. It seems like get 'page/:page' => 'issues#index', :page => %r{\d+} got added as a named route, which shouldn't be. I'll make a failing test case and a patch for it :)

    Thank you for reporting in. Meanwhile, I think you can solve your problem by adding :as to your route like this:

    resources :issues do
      collection do
        get 'page/:page' => 'issues#index', :page => %r{\d+}, :as => 'issues_page'
      end
    end
    

    November 23rd, 2010 @ 01:44 PM

  10. Prem Sichanugrist (sikachu)
    Prem Sichanugrist (sikachu)
    • Importance changed from Low to Medium

    November 23rd, 2010 @ 01:44 PM

  11. José Valim
    José Valim

    Prem is right, Rails in fact tries to generate routes automatically, but in this case it is generating the wrong one.

    November 23rd, 2010 @ 01:59 PM

  12. Repository
    Repository
    • State changed from open to resolved

    (from [731ca00b484379661786fac36c17db7e085603c4]) Dynamically generaeted helpers on collection should not clobber resources url helper [#6028 Routing breakage in 3.0.3 state:resolved] https://github.com/rails/rails/commit/731ca00b484379661786fac36c17d...

    November 25th, 2010 @ 10:50 AM

  13. Repository
  14. Wincent Colaiuta