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")
Related
I hoped route constraints would allow me to have admin.example.com/widgets/ and example.com/admin/widgets/ be effectively the same, with the URL helper admin_widgets_path pointing to the correct one based on the current subdomain. However, the route helper seems to only point to one or the other regardless of constraints.
Am I doing something wrong? Is this a bug? Is there a better way to solve this problem?
A rails app with an example of the problem has been published here, but the relevant details are below.
My config/routes.rb
class Subdomains
# any domain that starts with 'admin.'
def self.admin?
-> (request) { request.host =~ /^admin\./ }
end
# any other domain
def self.primary?
-> (request) { !admin?.(request) }
end
end
Rails.application.routes.draw do
constraints(Subdomains.primary?) do
namespace :admin do
resources :widgets
end
end
constraints(Subdomains.admin?) do
scope module: "admin", as: :admin do
resources :widgets
end
end
root to: "admin/widgets#index"
end
My app/views/admin/widgets/index.html.erb
<%= link_to "Widgets", admin_widgets_url %>
In this configuration, admin_widgets_url always returns /admin/widgets/ which isn't a valid route on admin.example.com so clicking the link results in a routing error on that subdomain. If the admin subdomain constraint block is put first in the routes, then the URL helper always returns /widgets/, breaking the link on a non-admin domain.
The routes otherwise work, but not sure how to get the URL helpers to point to the correct one.
Your expectations are wrong - Rails reads the entire routes definition as part of the setup phase. This is also when the route helpers are created. The constraint is not actually evaluated until later when the request is matched.
Rails splits this into distinct phases to allow forking.
Instead you may need to override the route helpers.
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
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.
I have 2 tables news and news_type. Also I have 3 base types for news: politic, technology and sport. How I can filter this data by subdomain?
For example:
If I go to example.com I get all news in my home page
If I go to sport.example.com I get all news with type sport
And so on.
In your routes.rb file, you can provide constraints on the path. For example:
# routes.rb
...
get "/" => "sports#index", constraints: { domain: "sport.example.com" }
get "/" => "tech#index", constraints: { domain: "tech.example.com" }
root :to => 'static#index'
This would route
sport.example.com to the index action within SportsController
tech.example.com to the index action within TechsController
example.com to the index action within StaticController
Thanks Tyler for the answer, however I came across a constraint method which allows a more dynamic solution for subdomains for lets say the tags that are assigned to a news_item i.e: "rugby.example.com" would fetch all news_items with the rugy tag attached.
# routes
constraints(Subdomain) do
match "/" => "news_items#index"
end
#lib/subdomain.rb
class Subdomain
def self.matches?(request)
request.subdomain.present? && request.subdomain != "www"
end
end
# news_items_controller.rb
def index
#item_tag = ItemTag.find_by_title(request.subdomain)
#news_items = #item_tag ? #item_tag.news_items : NewsItem.all
end
important to note is that this needs to be declared above your traditional root declaration.
I want to validate an IP address before I login in my Admin Controller.
before_filter :validate_ip
How should I create this IP validation? I just want to write the IP addresses that have access in the controller.
The first solution is dynamic, because you can populate the array in runtime:
def validate_ip
valid = %w[127.0.0.1 10.0.0.1]
redirect_to(login_path) unless valid.include?(request.remote_ip)
end
The other way to restrict IPs is, to use constraints in your routes.rb:
get '/admin' => 'admin#super_secure_action', :constraints => { :ip => %w[127.0.0.1 10.0.0.1] }
# or
constraints(:ip => %w[127.0.0.1 10.0.0.1]) do
# Every route defined in here will be restricted.
get '/admin' => 'admin#super_secure_action'
end
For more information about Rails and Routes, be sure to check out: Rails Dispatch - The Powerful New Rails Router.