How to constrain a Rails route based on an array of subdomains? - ruby-on-rails

I have a Rails 4.2 application with a set of routes that are constrained to a subdomain.
constraints subdomain: 'admin' do
# ...
end
However, I'm not sure how to specify multiple subdomains (both admin and admin.staging). How can I specify multiple subdomains?

Even though it's not documented, you can also pass an array of subdomains:
constraints subdomain: ['admin', 'admin.staging'] do
# ...
end

You can use a regular expression, e.g.:
constraints subdomain: /^admin|admin\.staging$/ do
# ...
end
...or...
constraints subdomain: /^admin(\.staging)?$/ do
# ...
end
You can also use a lambda:
constraints subdomain: ->(req) { %w[ admin admin.staging ].include?(req.subdomain) } do
# ...
end
You can read the documentation for constraints here: http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html#method-i-constraints

Related

Ruby on Rails - Excluding a single domain from some routes

In Rails route, usually we do constraints: {domain: "example.com" } if we want to specify specific routes that example.com can have. But how do I reverse this such that everyone can access this except example.com
You can create custom constraint. Add more excluded domains if you want.
class DomainConstraint
def matches?(request)
excluded_hosts = ['example.com']
excluded_hosts.exclude?(request.host)
end
end
Use it like this:
constraints DomainConstraint.new do
..
end
https://guides.rubyonrails.org/routing.html#advanced-constraints

Constraints not working when using block syntax in Rails

I tried to add constraints to a group of scoped routes like so:
constraints locale: 'de' do
scope 'magazin' do
get '', to: 'magazine#index', as: 'magazine'
# more routes
end
end
It doesn't make use of the restriction.
Whereas putting the restriction to a single route works as expected.
get '', to: 'magazine#index', as: 'magazine', constraints: { locale: 'de' }
I tried to use the constraints block in different positions, inside and outside the scope block. Without any change in the result.
The Rails Guide for Routing has this example which I pretty much copied:
namespace :admin do
constraints subdomain: 'admin' do
resources :photos
end
end
Any ideas what's wrong with the code?
Without having the whole routes.rb file it is hard to say why it doesn't work as expected.
Is it possible you have some kind of scope defined for locale??
Imagine sth like
scope '/:locale', locale: /de|en/ do
# lots of routes so you are not aware of the scope
constraints locale: "de" do
scope 'magazin' do
get '', to: 'magazine#index', as: 'magazine'
end
end
end
With this your are actually setting a constraint to locale to be either de or en. The constraint from the scope has precedence over the constraints block.
While this is not clear from the rails guide I found a merge request that proves my argumentation.

Multiple level subdomain configuration in Ruby on Rails

How can I have multi level sub domains in ruby on rails ?
Currently if I want to create a sub domain, I configure this is routes.rb.
constraints :subdomain => 'my' do
mount API => '/'
mount GrapeSwaggerRails::Engine => '/documentation'
end
This will create support for my.domain.com
However, if I wish to have another level api.my.domain.com, what can i do to have one more level of subdomain in the routes ? Thanks.
You could nest your subdomain definitions. The subdomain constraints can be regular expressions so you could do something like
constraints subdomain: /.*my/ do
constraints subdomain: 'api.my' do
mount API => '/'
mount GrapeSwaggerRails::Engine => '/documentation'
end
# Non-API my subdomain routes
end

Rails routing more complex :constraints

Currently if you wish to add a constraint there are many ways to do it but as I see currently you can only include one definitive method which is called. E.g.
Class Subdomain
# Possible other `def`s here, but it's self.matches? that gets called.
def self.matches?( request )
# Typical subdomain check here
request.subdomain.present? && request.subdomain != "www"
end
end
The problem with the above approach is it doesn't handle routes prefixed in www, that is admin and www.admin are indistuingishable. More logic can be added, but if this was required over a set of static subdomains like admin, support, and api you currently need to make SubdomainAdmin, SubdomainSupport etc....
This can be solved with regex as follows in routes.rb:
admin
:constraints => { :subdomain => /(www.)?admin/ }
api
:constraints => { :subdomain => /(www.)?api/ }
If requests were even more complex than this things get tricky. So is there a way to add individual methods inside a class used for constraints?
Essentially, how is the below achieved? Is it even possible? Whats the best method of white-listing subdomains to use?
E.g.
Class Subdomain
def self.admin_constraint( request )
# Some logic specifically for admin, possible calls to a shared method above.
# We could check splits `request.subdomain.split(".")[ 1 ].blank?` to see if things are prefixed with "www" etc....
end
def self.api_constraint( request )
# Some logic specifically for api, possibly calls to a shared method above.
# We could check splits `request.subdomain.split(".")[ 1 ].blank?` to see if things are prefixed with "www" etc....
end
def self.matches?( request )
# Catch for normal requests.
end
end
With which we can now call constraints specifically as follows:
:constraints => Subdomain.admin_constraints
And all generic constraints as follows:
:constraints => Subdomain
Is this possible in Rails 4.0.3?
The router will call the #matches?(request) method on whatever object you pass the route. In the case of
:constraints => Subdomain
you're giving the route the Subdomain Class object. However, you could also pass an instance, which you could configure via arguments. e.g.,
Class Subdomain
def initialize(pattern)
#pattern = pattern
end
def matches?(request)
request.subdomain.present? && #pattern ~= request.subdomain
end
end
# routes.rb
namespace :admin, constraints: Subdomain.new(/(www.)?admin/) do
# your admin routes here.
end
NOTE: I didn't verify that code works, I just wrote it off the top of my head, so consider it more of pseudo-code than implementation ready.
Also, you can see an example of this technique, with some more details, at: Use custom Routing Constraints to limit access to Rails Routes via Warden.

Ruby on Rails: Routing for subdomain and custom domain possible?

I'm looking to route users' pages from their subdomains and also their custom domains. For example, consider three domains:
app.com
user1.app.com
user1.com
A visitor should be able to see the user's page at both the subdomain from the app's domain (user1.app.com) as well as the user's custom domain (user1.com). That is, a visitor will visit the user page when visiting any subdomain of "app.com" or a root domain that is NOT "app.com".
How would I set up routes to do so?
Maybe something along the lines of this pseudo-code:
match "/", :to => "user_page#show", :constraints => { :subdomain => /.+/ OR :domain => NOT(app.com) }
What do you think?
use a constraint utility class or module.
module DomainConstraint
def self.matches? request
request.subdomain.present? || request.domain != 'app.com'
end
end
constraints DomainConstraint do
# routing here
end
if your constraint only applies to one route, you can do :
resources :foo, constraints: DomainConstraint
note : your utility class can also be replaced by a simple lambda (see "Dynamic request matching")

Resources