Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wildcard route with a constraint to end with trailing slash

I have 2 wildcard routes that look like:

get '*country_path/', to: 'country#list', constraints: { country_path: /\/$/ }
get '*country_path/:title', to: 'country#show'

country#list should be called when the url has a trailing slash. Examples:

www.example.com/usa/california/
www.example.com/usa/california/abc/

country#show should be called when it does NOT have a trailing slash. Examples:

www.example.com/usa/californa/travel
www.example.com/usa/californa/average-income

Currently my constraint doesn't seem to work as all requests go to country#list.

What is wrong with my route constraint?

like image 359
Blankman Avatar asked Jul 02 '14 18:07

Blankman


2 Answers

You should ask yourself whether this is really the best way to accomplish what you are trying to do. Websites in general do not care about trailing slashes in directories. For example:

www.facebook.com/username

Will take you to the same place as:

www.facebook.com/username/

The vast majority of websites work like this, so what you are trying to do right now is against web conventions. It will certainly have ramifications for SEO, if you care about that. Even if you don't, you should think about the difficulties you will face when testing these routes. Your tests will be brittle and this will cause a lot of frustration in the long run.

My advice would be this: if you want to distinguish between list and show, put those into the URL. For example:

www.example.com/usa/california/list
www.example.com/usa/california/abc/list

Can take the user to country#list, with "california" and "abc" as parameters. Similarly:

www.example.com/usa/californa/travel/show
www.example.com/usa/californa/average-income/show

Can take the user to country#show. This is significantly easier to do. It is also more standard and user-friendly. For more information, see Rails Routing from the Outside In.

like image 50
Ege Ersoz Avatar answered Nov 14 '22 22:11

Ege Ersoz


The trouble is that Rails has stripped the trailing / before it gets to the routing, so it's not possible to match on it using the conventional methods. The only way to do this is to use an advanced constraint. The following will work for what you want:

# config/routes.rb
class TrailingSlashMatcher
  def matches?(request)
    uri = request.env["REQUEST_URI"]
    uri.present? && uri.end_with?("/")
  end
end

Rails.application.routes.draw do
  get '*country_path', to: 'country#list', :constraints => TrailingSlashMatcher.new
  get '*country_path/:title', to: 'country#show'
end

(taken from here).

The trouble with this approach is that it won't work in all circumstances. It works for real requests to a server, but it may well have issues when being called from tests etc. The reason for this is that the REQUEST_URI env variable isn't part of the rack spec, therefore isn't set by the test framework. env["PATH_INFO"] is mandatory, but that has the trailing / removed, so is no good here.

All in all, it may be worth reconsidering the design of your routes. Matching on the presence or absence of a trailing / is fairly brittle. It could also lead to some confusion for your users (e.g. when links get copied/pasted etc). Is there no other way to differentiate between list and show requests (maybe based on the depth of the route).

like image 34
AlexT Avatar answered Nov 14 '22 22:11

AlexT