Rails 6: Catch all route constraint not working - ruby-on-rails

I placed a catch all route at the bottom of my route file. Annoyingly, Active Storage requests are also directed to the same action. I don't want that.
I have read this on SO and this issue on Github that suggest adding a constraint to the route.
Rails.application.routes.draw do
#...
get "/*slug" => "profiles#show",
param: :slug,
as: "profile",
contraints: lambda { |req|
req.path.exclude? "rails/active_storage"
}
end
However, this isn't working for me. ActiveStorage requests are still going to profiles#show. What is wrong with my constraint? Have I missed anything obvious?

You have a typo.
It should be constraints: instead of contraints:.

Related

How can I route a subdomain to rails app in routes.rb?

I have a url https://www.openhub.net which is my rails 4.2.7 app. I have a separate subdomain on another system which is https://code.openhub.net/. If you clink on the second link, this will reroute you to a discontinued page. What's hosting the code.openhub.net url is going to be decomissioned and will be rerouted to openhub.net. So what I'm trying to do is that when someone tries to go to code.openhub.net my rails app that has the openhub.net will serve a static page. The problem is that I can't conceptualize how this will work. For example, here is a snippet of rails documentation under routing.
3.9 Request-Based Constraints
You can also constrain a route based on any method on the Request object that returns a String.
You specify a request-based constraint the same way that you specify a
segment constraint:
get 'photos', to: 'photos#index', constraints: { subdomain: 'admin' }
You can also specify constraints in a block form:
namespace :admin do
constraints subdomain: 'admin' do
resources :photos
end
end
Request constraints work by calling a method on the Request object
with the same name as the hash key and then compare the return value
with the hash value. Therefore, constraint values should match the
corresponding Request object method return type. For example:
constraints: { subdomain: 'api' } will match an api subdomain as
expected, however using a symbol constraints: { subdomain: :api } will
not, because request.subdomain returns 'api' as a String.
I understand what the document is saying but when I try to put this code into practice, I'm stumped. Here is my routing.rb file:
get '???', to: '???', constraints: { subdomain: 'code' }
I understand that the subdomain constraint portion of the url will be code but will the get and to be? How do I know will go under get? Do I simply substitute get code.openhub.net which will route to a CodeController#index? Has anyone tried to do something like this before? From the documentation I see that the request object has an original_url method that I can call. Is there perhaps a way I can make use of this? Any help would be greatly appreciated.
As a sidenote, I noticed that subdomain is not a property of the request object outlined here Rails Request Object
This should work:
get '/', to: 'code#index', constraints: { subdomain: 'code' }
That will route requests to https://code.openhub.net/ to the the index method in your CodeController.
However, the URL will still say https://code.openhub.net/. If you don't want that, you might try a redirect, like this:
get '/', to: redirect('https://www.openhub.net/'), constraints: { subdomain: 'code' }

Multiple 'root to' routes in rails 4.0

I am trying to get rails to go to different controller#action according to the subdomain, and this is what I have so far in routes.rb
Petworkslabs::Application.routes.draw do
get '/', to: 'custom#show', constraints: {subdomain: '/.+/'}, as: 'custom_root'
get '/', to: "welcome#home", as: 'default_root'
end
rake shows the correct routes I want it to take
rake routes
Prefix Verb URI Pattern Controller#Action
custom_root GET / custom#show {:subdomain=>"/.+/"}
default_root GET / welcome#home
But for some reason, I can't get requests like abc.localhost:3000 to hit the custom controller. It always routes it to welcome#home. Any ideas? I am fairly new to rails, so any tips about general debugging would also be appreciated.
EDIT: I stepped through the code using the debugger and this is what I found
(rdb:32) request.domain
"abc.localhost"
(rdb:32) request.subdomain
""
(rdb:32) request.subdomain.present?
false
Looks like for some reason rails thinks that the subdomain is not present, even though its there. I wonder if its because I am doing this localhost.
Updated Answer:
Worked for me on Rails 3 & 4:
get '/' => 'custom#show', :constraints => { :subdomain => /.+/ }
root :to => "welcome#home"
#manishie's answer is right, but you'll still likely have issues in your devo environment if you're using localhost. To fix it add the following line to config/environments/development.rb:
config.action_dispatch.tld_length = 0
and then use #manishie's answer in routes.rb:
get '/' => 'custom#show', :constraints => { :subdomain => /.+/ }
root :to => "welcome#home"
The issue is that tld_length defaults to 1 and there's no domain extension when you're using localhost so rails fails to pickup the subdomain. pixeltrix explains it really well here: https://github.com/rails/rails/issues/12438
For some reason request.subdomain was not getting populated at all (I suspect this is because I have doing this on localhost, I have opened a bug here https://github.com/rails/rails/issues/12438). This was causing the regex match in routes.rb to fail. I ended up creating a custom matches? method for Subdomain which looks something like this
class Subdomain
def self.matches?(request)
request.domain.split('.').size>1 && request.subdomain != "www"
end
end
and hooking that up in routes.rb
constraints(Subdomain) do
get '/', to: "custom#home", as: 'custom_root'
end
this seems to work.
EDIT: More information in the github issues page https://github.com/rails/rails/issues/12438

Set Up Route for Accessing Private S3 Content

I've been following
https://github.com/thoughtbot/paperclip/wiki/Restricting-Access-to-Objects-Stored-on-Amazon-S3
and
Rails 3, paperclip + S3 - Howto Store for an Instance and Protect Access to try and get Paperclip's expiring links to work. I believe most of what I'm running into is one of the routing variety.
In my pieces_controller I put a method in like this
def download
redirect_to #asset.asset.expiring_url(1000)
end
And then in my routes, I put this:
match "pieces/download"
Then in my view I have:
<%= link_to download_asset_path(piece)%>
It would seem to be far from working, and I'm not sure what is messed up. I know I'm getting routing errors for one, but it's also telling me that my download_asset_path is undefined, which is likely also routing related... I feel like I'm doing everything all wrong.
Tearing my hair out. Thanks!
Try modifying your routes file to:
match 'pieces/download' => 'pieces#download', :as => 'download_asset'
Your match needs to tell which controller#action to go to, and the as option will allow you to name the route download_asset_path.
If your pieces controller is for a Piece resource it could be cleaner like:
resources :pieces do
member do
get :download
end
end
But then you would want to change the link to:
link_to 'Link text', download_piece_path(piece)
For further reading: http://guides.rubyonrails.org/routing.html

ROR route with parentheses in constraint

I'm trying to create a rails route for movies (on the root path) that has parentheses containing the movie's year in it.
E.g. Men in black => "/men-in-black-(1997)"
My route is:
resources :movies,
path:'/',
only:[ :index, :list, :show ],
constraints: { id: /[A-Za-z0-9-]+\(\d{4}\)/ }
When I use this route (movie_path(Movie.first)), I get
"ActionController::RoutingError: No route matches: ..."
When I change the route constraint to:
constraints: { id: /[A-Za-z0-9-]+\\\(\d{4}\\\)/ }
the route works when using the url routing helper. However, the route doesn't work for the reverse mapping (e.g. taking "/men-in-black-(1997)" and routing it to the correct action/controller). When I run (from console):
Rails.application.routes.recognize_path("/men-in-black-(1997)")
I get the same routing error:
ActionController::RoutingError: No route matches
The problem seems to be associated to how rails escapes regex's in routing. For escaping with \( the object-to-route map fails, but url-to-route works. But when escaping with \\\( it is the opposite.
Anyone have any tips or experience with this?
As a workaround hack you could try:
constraints: { id: /[A-Za-z0-9-]+(\\\(\d{4}\\\)|\(\d{4}\))/ }
That is, make the constraint accept either, if it accepts one in one case and the other in the other case.
Which is to say: that's weird, I have no idea why Rails would do that or how to fix it ;)
Well I don't have a lot of experience writing regex constraints, but you could always do a wildcard route and then sanitize in the controller.
match 'movies/*movie' => 'movie#action'
This will give you access to a :movie param with all the characters input

Rails route if no route matched

Is there a solution to redirect the user to a specific controller#action if no route matched? I had a "last rule" like this:
match ":rest" => "application#notfound", :constraints => { :rest => /.*/ }
to redirect all "other" stuff to application#notfound. The problem is that plugins with own routes where set below shis rule and never gets called.
Make a catch-all route and put it as the last rule in your routes.rb file.
match "*rest" => "application#notfound"
you also get whatever the path was as a parameter thru params[:rest] in your controller.
You are definitely not the only person having this problem. I know it's frustrating, but give it some time - I'm confident the Rails team will put together something simple and elegant. I'm in a similar position, only I need to know the incorrect url resource the user entered.
A quick Google search and I found this article demonstrating a full solution to the Rails 3.0 rescue_from ActionController::RoutingError problem. The solution involves Route Globbing and solves my need to both handle incorrect urls and capture the url entered. Hope this helps!
Put this rule last.. Routing matches from top to bottom so if it did find a match then it stops there.
Maybe it's not the best approach, but it will works while you don't find better solution
for rails 2.3, at the application controller, catch the exception like
rescue_from(ActionController::RoutingError) { redirect_to "/xpto" #add your code here }
Rails 3
At initializer put a code like:
module ActionDispatch
class ShowExceptions
def render_exception(env, exception)
if exception.kind_of? ActionController::RoutingError
render(500, 'it was routing error')
else
render(500, "some other error")
end
end
end
end
or something more clean like:
https://gist.github.com/522944
https://gist.github.com/522924
or:
match '/:anything', :to => "home#routing_error", :constraints => { :anything => /.*/ }
but maybe this last match won't be good for you as it'll override all routes from plugins/engines. I think that best solution is to use custom rack middleware for handling special cases.

Resources