Stop rails subdomain from triggering on *everything* - ruby-on-rails

My site has some pages, such as example.com/about
I have also created an API for my app at api.example.com.
# API
namespace :api, path: '/', constraints: { subdomain: 'api' } do
scope format: true, constraints: { format: 'json' } do
get 'posts/latest', to: 'posts#latest'
get 'posts/:id', to: 'posts#show'
end
end
# Pages
get 'about' to: 'pages#about'
get 'contact', to: 'pages#contact'
The problem is, now my /about page is working on my api subdomain.
api.example.com/about should definitely not work like that, and should trigger a 404.
What I really want to do is wrap the entire rest of my routes file in constraints: { subdomain: nil } but I want to know the "Rails Way" to do this.
Thanks!

What I really want to do is wrap the entire rest of my routes file in constraints: { subdomain: nil } but I want to know the "Rails Way" to do this.
This is the Rails way.
A typical Rails app will just listen to a socket and not care about domains. Usually your webserver will handle the mapping between domains/IP addresses and internal application sockets (e.g. Apache VirtualHost).
When the webserver passes an HTTP request to the application, Rails then is only interested in everything but the (sub-) domain, unless configured otherwise.

Related

Ruby - A request route with a wildcard doesn't work

Could someone suggest why these 2 routes aren't the same:
get('/:id/' => 'outlets/play#show', :as => :listen, constraints: { id: /thetrack-a123-bay7623/ } )
get('/:id/' => 'outlets/play#show', :as => :listen, constraints: { id: /thetrack-.*/ } )
What I'm trying to achieve is only want that route outlets/play#show to be used when there is an :id that begins with thetrack.
I've found that if I explicitly use those characters its fine i.e. without thetrack in the route it doesnt use that route. However if I use thetrack-.* it still goes into the outlets/play#show route despite thetrack not being present in my request.
Any ideas?
I've tried other regex patterns e.g.
thetrack-.+
thetrack-.+-.+
thetrack-.*-.*
with no luck
If what you are trying to do is route any request /:id/ with ID starting with thetrack- to outlets/play#show, then your configured route should work:
get '/:id/', to: 'outlets/play#show', as: :listen, constraints: { id: /thetrack-.*/ }
Here are some example paths that will route to outlets/play#show using this wildcard:
/thetrack-
/thetrack-a123
/thetrack-a123-bay7623
/:id/ is quite broad reaching. Check that you have not got any other conflicting routes. i.e. another route at root level /:something/ that could be catching the other requests where thetrack- is not specified.

How does routes of Ruby On Rails know which controller to hit when we hit an url

How ROR routes recognise which controller method to hit while we hit an url.
It's a way to redirect incoming requests to controllers and actions. It replaces the mod_rewrite rules. Best of all, Rails' Routing works with any web server. Routes are defined in app/config/routes.rb.
Think of creating routes as drawing a map for your requests. The map tells them where to go based on some predefined pattern.
The routes.rb file defines the actions available in the applications and the type of action such as get, post, and patch.
like:
get 'photos/:id' => 'photos#show', :defaults => { :format => 'jpg' }
the normalise value of above route is.
app: #<ActionDispatch::Routing::RouteSet::Dispatcher:0x007fd05e0cf7e8
#defaults={:format=>"jpg", :controller=>"photos", :action=>"show"},
#glob_param=nil,
#controller_class_names=#<ThreadSafe::Cache:0x007fd05e0cf7c0
#backend={},
#default_proc=nil>>
conditions: {:path_info=>"/photos/:id(.:format)", :required_defaults=>[:controller, :action], :request_method=>["GET"]}
requirements: {}
defaults: {:format=>"jpg", :controller=>"photos", :action=>"show"}
as: nil
anchor: true
The official Ruby on Rails documentation explains this question in a thorough and understandable way:
http://guides.rubyonrails.org/routing.html

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

How to override Rails app routes from an engine?

I have a Rails app that I am trying to integrate a Rails engine in to.
The host app has some catch all routes:
# magic urls
match '/' => 'admin/rendering#show'
match '*path/edit' => 'admin/rendering#show', :defaults => { :editing => true }
match '*path' => 'admin/rendering#show'
It looks like the engine routes are loaded after the application catches all routes.
/sitemap.xml(.:format) {:format=>"xml", :controller=>"admin/sitemaps", :action=>"show"}
/(.:format) {:controller=>"admin/rendering", :action=>"show"}
/*path/edit(.:format) {:controller=>"admin/rendering", :action=>"show"}
/*path {:controller=>"admin/rendering", :action=>"show"}
engine_envs GET /engine/envs/:id(.:format) {:controller=>"engine/envs", :action=>"show"}
PUT /engine/envs/:id(.:format) {:controller=>"engine/envs", :action=>"update"}
jammit /assets/:package.:extension(.:format) {:extension=>/.+/, :controller=>"jammit", :action=>"package"}
So far, everything is hitting the /engine/envs routes are getting caught by the application catch all routes. However I see that the jammit route is loaded after the engine and I don't believe those are getting caught. Any way to override the app routes?
You could stick your engine routes in a method and then call that in your host app.
# engine routes.rb
module ActionDispatch::Routing
class Mapper
def engine_routes
engine_envs GET /engine/envs/:id(.:format)
# ...
end
# ...
and then in your host app add the method before the catch-all route
# host app routes.rb
MyTestApp::Application.routes.draw do
# ...
engine_routes
match '/' => 'admin/rendering#show'
match '*path/edit' => 'admin/rendering#show', :defaults => { :editing => true }
match '*path' => 'admin/rendering#show'
end
Routes are used in the order they are defined. The first routes to be read are the one of the host application, then of your engine.
As soon as a matching route is found, the search for a route is stopped.
As far as I know, there are no way (I may be wrong about this) to override this feature other than to change your "mag
UPDATE: So that means that the order you see them in "rake routes" is the order they are processed. As soon as a matching route is found, there you go.

Resources