Ruby on Rails 3: Multiple routes in same namespace
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', :contro开发者_Python百科ller => "site", :action => 'show'
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.
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 :-)
My page urls are like '/about' and my city urls are like '/san-francisco'.
If you don't namespace the urls of two different types of object, there is no way to tell them apart, without running an extra check against the db (expensive) or a hard-coded list of cities/pages (fragile) each time you receive a request for any page/city on your site.
If you can possibly namespace the urls, that's a better way to do it - so have either:
cities/san-fransisco
or
pages/about
depending on which you feel it is most important to keep at the root level. If there is a /cities page anyway, it's natural for your users to keep them under that and it lets them easily move up to the cities page and see the relation between the urls, plus lets you later show cities/top etc, etc. Routes would then be obvious
get "cities/:slug", :to => "cities#show", :as => city
get ":slug", :to => "site#show", :as => page
and avoid any problems with conflict.
If you must keep cities at root level, you could put pages under /pages and leave cities as the root level. Otherwise you're stuck doing more work deciding on routes (a lookup each time a route comes in).
Another thing you could consider is adding a numeric id to cities, this will help you deal with clashing names as well (quite common in the USA, very very common in the full world), so that you have
/1-san-francisco
/2-san-francisco (San Francisco, Minnesota)
and for routes:
get ":slug", :to => "cities#show", :as => city, :constraints => { :slug => /\d*-[\w]*/ }
get ":slug", :to => "site#show", :as => page
Or namespacing cities by country (us/state/ etc) - if you have no way of differentiating them you will no doubt run into duplicate city names at some point.
精彩评论