开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜