views:

28

answers:

2

Hey! I am trying to set up routes in a Rails application so that, depending on the type of parameter passed, Rails sends the request to a different action.

I have courses which have an attribute state which is a string with a two letter state abbreviation. When a user visits /courses/1, I want Rails to display the show action in the courses controller (and pass the parameter as :id). When a user visits /courses/CO though, I want Rails to display the index action and pass the parameter as :state.

So /courses/1 would be equivalent to

:controller => 'courses', :action => 'show', :id => '1'

And /courses/CO would be equivalent to

:controller => 'courses', :action => 'index', :state => 'CO'

I have tried this:

map.resources :courses, :except => { :index, :show }
map.connect 'courses/:state', :controller => 'courses', :action => 'index', :state => /[A-Z]{2}/
map.connect 'courses/:id',    :controller => 'courses', :action => 'show', :id => /[0-9]+/

But it breaks (the rails server wont even start). I don't usually do things like this with routes, so I am outside of my know-how. Thanks!

Edit: Fixed a typo, thanks JC.

Current solution looks like this:

map.resources :courses, :except => [ :index, :show ]
  map.courses  '/courses',        :controller => 'courses', :action => 'index', :state => 'AL', :method => :get
  map.courses  '/courses/:state', :controller => 'courses', :action => 'index', :requirements => { :state => /[A-Z]{2}/ }, :method => :get
  map.course   '/courses/:id',    :controller => 'courses', :action => 'show',  :requirements => { :id => /[0-9]+/      }, :method => :get
A: 

I'm afraid this won't work since the structure that maps paths to controllers and actions is setup on start of the rails application, parameter handling happens at request time.

What you could do is to match the :id-parameter in the show-action of the CoursesController against a list of valid states and then either redirect or render a different action.

Hope this helps.

flitzwald
+2  A: 

This works, but you will need to go edit all your links to the index to say things like courses_path('AA') and you won't be able to use some of the nice helpers, like form_for, which assume you are following the convention that #create is simply #index with a POST request. (Get comfortable with form_tag)

ActionController::Routing::Routes.draw do |map|
  map.resources :courses, :except => [ :index, :show ]
  map.courses  '/courses/:state', :controller => 'courses', :action => 'index', :requirements => { :state => /[A-Z]{2}/ } , :method => :get
  map.course   '/courses/:id',    :controller => 'courses', :action => 'show',  :requirements => { :id => /[0-9]+/      } , :method => :get
end

It will keep your routes named the same, though.

(by the way, your /co does not match your regex, which requires upper case chars)


Fun aside: Do we really need the abstraction of a router? http://blog.peepcode.com/tutorials/2010/rethinking-rails-3-routes

Joshua Cheek
Would allowing for a default case (no parameter) prevent me from having to change all of those links? EG:`map.connect '/courses', :controller => 'courses', :action => 'index', :state => 'AL', :method => :get`
James