views:

63

answers:

2

My page urls are like '/about' and my city urls are like '/san-francisco'. There is no overlap. How can I get the following routes to play nice?

get '/:city_slug', :to => 'cities#show', :as => 'city'
get ':action', :controller => "site", :action => 'show'
+1  A: 

There's no way you can do this with straight out routing. You could work around it by defining that when a City cannot be found that you then redirect to the show page of the sites controller, passing through the page name. That would require something like this in the routes:

get ':slug', :to => "cities#show", :as => "city"
get ':page', :to => "site#show", :as => "page"

In the show action for CitiesController you would do the find and then rescue from it when it raises an ActiveRecord::RecordNotFound.

def show
  @city = City.find_by_slug!(params[:slug])
  rescue ActiveRecord::RecordNotFound
    redirect_to(page_path(params[:slug])
end

Or you know, you could just define a route for each of your pages and stop this redirect malarky.

get 'about', :controller => "site", :action => "show", :id => "about"
# and so on.
Ryan Bigg
A: 

I strongly disagree with the "redirect" part of @ryan's answer. It would double your server communication - it will work but it's definitely not a nice solution. The idea of reordering is perfectly correct.

So there is my solution - I'm really not sure if it is ok, but it seems to work somehow.

In your routes.rb:

get ":slug", :to => "cities#show", :constraints => CityConstraint.new, :as => city
get ":slug", :to => "site#show", :as => page

There are two things to mention in the code:

  • :slug in the second case - that's quite strange but it has to be named in a same way as in the first route. I was not able to manage it to work with :page as in your case. But I think that's not a big deal
  • :constraints - this is normal and documented

The constraint itself consists of a class like the following:

class CityConstraint
  def matches?(request)
    # The condition would normally go through ActiveRecord to test the 
    # existence of town dynamically
    towns = %w(london prague washington)
    towns.include?(request.params[:slug])
  end
end

The constraint runs (the method matches?) every time, when there is a hit in the routing table on the rule with the constraint. I believe that it is what you need. Probably some form of cache would be necessary not to kill the performance.

And by the way - this solution is only theoretical and I've never used it in production. It would be great if someone will test it :-)

pawien