Search code examples
ruby-on-railsroutesruby-on-rails-3.1glob

Empty route globs in Rails 3


I'm upgrading from Rails 2 to 3.1, and having some problems with route globs. We had working globbed routes in Rails 2 that were like this:

map.connect '/foo/*bar', :controller => 'foo', :action => 'index'

A GET to '/foo/one/two' would give the following parameters at the controller side:

{
  :controller => 'foo',
  :action => 'index',
  :bar => ['one, 'two']
}

Empty globs were fine, so we could also do GET '/foo' and get:

{
  :controller => 'foo',
  :action => 'index',
  :bar => []
}

Upgrading to Rails 3.1, there are a few differences:

match '/foo/*bar', :to => 'foo#index'

The globbed route segment comes through as a string instead of an array, but I've dealt with that in my code. No problem there. A GET '/foo/one/two' gives:

{
  :controller => 'foo',
  :action => 'index',
  :bar => 'one/two'
}

The problem comes when I have nothing in the glob segment. GET '/foo' is no longer routable, and if I try to generate the URL from parameters, it cannot generate. Basically, if params[:bar] is an empty string, it just completely fails to route.

This is awkward in my app, as it depends on the Rails 2.3 behaviour to produce sensible URLs. We also have instances where the glob is mid-path:

match '/foo/*bar/show', :to => 'foo#show'

This means that we had paths like:

/foo/one/two/show
/foo//show

Is there any way to get the routing engine to handle the empty strings as it used to in Rails 2.3? I've tried adding extra routes for the empty-glob version, which works in the simple trailing-glob case, but in the case where the glob is mid-path, it doesn't.


Solution

  • I get tests passing with these routes:

    Your::Application.routes.draw do
      match '/foo/', :to => 'foo#index', :bar => ''
      match '/foo//show', :to => 'foo#show', :bar => ''
      match '/foo/*bar/show', :to => 'foo#show'
      match '/foo/*bar', :to => 'foo#index'
    end
    

    Sequence is important!

    Ambiguity: Is /foo/show meant to "show foo-index", or do "index for bar=show"?

    Test:

    require_relative '../test_helper'
    
    class FooRoutingTest < ActionController::TestCase
      test 'root index' do
        assert_recognizes({:controller => 'foo', :action => 'index', :bar => ''}, '/foo')
        assert_recognizes({:controller => 'foo', :action => 'index', :bar => ''}, '/foo/')
      end
    
      test 'root show' do
        assert_recognizes({:controller => 'foo', :action => 'show', :bar => ''}, '/foo/show')
        assert_recognizes({:controller => 'foo', :action => 'show', :bar => ''}, '/foo//show')
      end
    
      test 'wildcard index' do
        assert_recognizes({:controller => 'foo', :action => 'index', :bar => 'hello'}, '/foo/hello')
        assert_recognizes({:controller => 'foo', :action => 'index', :bar => 'hello/more'},'/foo/hello/more') 
      end
    
      test 'wildcard show' do 
        assert_recognizes({:controller => 'foo', :action => 'show', :bar => 'wow'}, '/foo/wow/show')
        assert_recognizes({:controller => 'foo', :action => 'show', :bar => 'wow/more'}, '/foo/wow/more/show')
      end
    end