Rails Routing Constraints and UTF-8 - ruby-on-rails

I need to handle routes like /:slug. The slugs are constrained elsewhere to match:
\A[\p{Alnum}_.-]{3,}\z
The obvious routes:
get '/:slug' => '...', :constraints => { :slug => /[\p{Alnum}_.-]{3,}/ }
get '/:slug' => '...', :slug => /[\p{Alnum}_.-]{3,}/
work fine with ASCII URLs like /mu-is-too-short and /where.is.pancakes.house but everything falls apart when the URL is /µ-is-too-short. The browser is sending this to the server:
/%C2%B5-is-too-short
and Rails is trying to check the encoded %C2%B5-is-too-short against the :slug constraint and failing because % is not a \p{Alnum}.
I have come up with two kludges:
get '/:slug' => '...', :slug => /.{3,}/
get '/:slug' => '...', :slug => /[^\/]+/, :constraints => lambda { |r| r.path_parameters[:slug].to_s =~ /\A[\p{Alnum}_.-]{3,}\z/ }
The :slug => /[^\/]+/ in the second is needed to keep Rails from thinking that the .b in a.b is a format extension.
Is there a way to make Rails behave sensibly and check the decoded :slug against the constraints or do I have to loosen the constraints or do it by hand?

You can use a lambda for the constraint and this way change the encoding (from http://www.intridea.com/blog/2011/2/21/use-lambdas-for-rails3-route-constraints):
scope :constraints => lambda{|req| !req.session[:user_id].blank? } do
# all my logged in routes
end

Related

Route cannot be found Rails 5.2.3 [duplicate]

How to force Rails to consider a param with a dot in the value like google.com (e.g. /some_action/google.com) a single param and not "id" => "google", "format"=> "com"?
The parameter value should be "id" => "google.com"
By default, dynamic segments don't accept dots - this is because the dot is used as a separator for formatted routes. However, you can add some regex requirements to the route parameters. Here, you want to allow the dots in the parameters.
match 'some_action/:id' => 'controller#action', :constraints => { :id => /[0-z\.]+/ }
And in rails 2.3:
map.connect 'some_action/:id', :controller => 'controller', :action => 'action', :requirements => { :id => /[0-z\.]+/ }
Relevent rails guides section
In Rails 4 I used:
get 'operation/:p1/:p2', to: 'operation#get', constraints: { p1: /[^\/]+/, p2: /[^\/]+/ }
it allows any character in both params (other than '/')
And when used with the resources notation, it can be done like this:
resources :post,
only: [ :create, :index, :destroy ],
constraints: { id: /[0-z\.]+/ }
Tested in Rails 4.1
We had similar case when we removed some part of an api path. Basically we went from /api/app/v1/* to /api/v1/*
We put this in our routes
match '/api/app/v1/*path', to: redirect(path: '/api/v1/%{path}'), via: :all
This was all fine except for some routes that ended with path params including dots. E.g. /api/v1/foo/00.00.100 where .100 got parsed into format and the remaining param only had the value 00.00
We guarded this with some constraint on the params.
put '/api/app/v1/foo/:version',
constraints: { version: /([0-9]+)\.([0-9]+)\.([0-9]+)/ },
to: redirect('/api/v1/foo/%{version}')
Edit: we use rails 5

How to Detect an Integer in Rails 3 Routes?

I would like to do just a little bit of extra logic in rotues.rb, that probably doesn't belong there, but it seems to make the most sense to me.
I have two conflicting routes. To be primitive:
match '/videos/:browseby' => 'videos#browse', :as => "browse_by"
Where :browseby is looking for a string, such as "Tags", to browse videos by tags.
However, (and most probably saw this coming) I also have my basic show resource (again in primitive form):
match '/videos/:id' => 'videos#show', :as => "video"
Where :id is looking for the integer for the video ID.
Is there a way to add a small bit of logic such as...
match '/videos/:id' => 'videos#show', :as => "video", :format(:id) => :integer
(Which is my hypothetical rails syntax, to help show what I'm looking for.)
I know I can munch this in the Controller level, but it makes more sense to me to handle it at the route level.
You could try using :constraints and a regex:
match '/videos/:id' => 'videos#show', :as => "video", :constraints => { :id => /\d/ }
match '/videos/:browseby' => 'videos#browse', :as => "browse_by"
You'll also want to make sure the looser :browseby version comes after the :id version. Note that regex constraints are implicitly anchored at the beginning so that would work as long as your :browseby values didn't start with a number.
If you have tags that do start with numbers then you could use an object for the constraint and then you could include anchors in your regex:
class VideoIdsOnly
def matches?(request)
request.path =~ %r{\A/videos/\d+\z}
end
end
match '/videos/:id' => 'video#show', :as => "video", :constraints => VideoIdsOnly.new
match '/videos/:browseby' => 'videos#browse', :as => "browse_by"

Handle rails route with GPS parameter

I'd like to create a route in my rails app to handle a gps-coordinate parameter. The intention is to find restaurants near the given position.
This is were I started:
match "/restaurants/near/:lat/:lng(/:range)", :to => "restaurants#near", :as => "near", :constraints => {:range => /\d+/}
It seems the router has problems with float parameters, an url like /restaurants/near/53.0123/10.5678 isn't recognized. Do you have a solution or best practice for handling GPS coordinates in rails urls?
Thank you!
The problem is caused because Rails try to use the "dots" for search for the format (.:format)
So, you can add some constraints to fix it, for example:
match "/restaurants/near/:lat/:lng(/:range)", :to => "restaurants#near", :as => "near", :constraints => {:lat => /\-?\d+(.\d+)?/, :lng => /\-?\d+(.\d+)?/ , :range => /\d+/}

Rails REST routing: dots in the resource item ID

I have following in my routes.rb:
resources :users, :except => [:new, :create] do
get 'friends', :as => :friends, :on => :member, :to => "users#friends"
end
and following in my user.rb:
def to_param
self.login
end
And when, for example, user with dots in login (for example 'any.thing') comes from facebook, rails gives routing error (no route found, I suppose that's because it recognises anything after dot as a format or because of route constraints). How can I come over this error?
The following constrain definition permit the dot in id as well as any character except slash.
Supported formats must be explicitly defined (here .html and .json) to not to be taken by id.
resources :foobars,
:constraints => { :id => /[^\/]+(?=\.html\z|\.json\z)|[^\/]+/ }
That constrain definition is worked with Rails 3.1
For earlier Rails versions you may need to backport look-ahead support in regin gem (it is vendored in rack-mount gem)
You could replace periods with another character:
def to_param
login.gsub(/\./,"-") # note: 'self' is not needed here
end
user = User.find_by_login("bart.simpson")
user_path(user) # => "/users/bart-simpson"
EDIT
You're right, this fails to deal with unique logins that map to the same value. Maybe a better way is to use segment constraints in the route:
match 'users/(:id)' => 'users#show',
:constraints => { :id => /[0-9A-Za-z\-\.]+/ }
This should allow "/users/bart-simpson" and /users/bart.simpson" to generate :id => "bart-simpson" and :id => "bart.simpson" respectively. You'd have to alter the regex to add all the acceptable characters for the URL.
Note that this is mentioned in the Rails Routing Guide, section 3.2:
By default dynamic segments don’t accept dots – this is because the
dot is used as a separator for formatted routes. If you need to use a
dot within a dynamic segment add a constraint which overrides this –
for example :id => /[^\/]+/ allows anything except a slash.
To allow the :id segment to contain any character except '/':
match 'users/(:id)' => 'users#show', :constraints => {:id => /[^\/]+/}
It's written elsewhere in one of the answers, but this is IMO the simplest way.

Rails — Params with "dot" (e.g. /google.com)

How to force Rails to consider a param with a dot in the value like google.com (e.g. /some_action/google.com) a single param and not "id" => "google", "format"=> "com"?
The parameter value should be "id" => "google.com"
By default, dynamic segments don't accept dots - this is because the dot is used as a separator for formatted routes. However, you can add some regex requirements to the route parameters. Here, you want to allow the dots in the parameters.
match 'some_action/:id' => 'controller#action', :constraints => { :id => /[0-z\.]+/ }
And in rails 2.3:
map.connect 'some_action/:id', :controller => 'controller', :action => 'action', :requirements => { :id => /[0-z\.]+/ }
Relevent rails guides section
In Rails 4 I used:
get 'operation/:p1/:p2', to: 'operation#get', constraints: { p1: /[^\/]+/, p2: /[^\/]+/ }
it allows any character in both params (other than '/')
And when used with the resources notation, it can be done like this:
resources :post,
only: [ :create, :index, :destroy ],
constraints: { id: /[0-z\.]+/ }
Tested in Rails 4.1
We had similar case when we removed some part of an api path. Basically we went from /api/app/v1/* to /api/v1/*
We put this in our routes
match '/api/app/v1/*path', to: redirect(path: '/api/v1/%{path}'), via: :all
This was all fine except for some routes that ended with path params including dots. E.g. /api/v1/foo/00.00.100 where .100 got parsed into format and the remaining param only had the value 00.00
We guarded this with some constraint on the params.
put '/api/app/v1/foo/:version',
constraints: { version: /([0-9]+)\.([0-9]+)\.([0-9]+)/ },
to: redirect('/api/v1/foo/%{version}')
Edit: we use rails 5

Resources