I'm using the apartment gem in a rails app.
I have one database with schemas for each tenant and one public schema for the Tenant table.
I have excluded the www subdomain:
Apartment::Elevators::Subdomain.excluded_subdomains = ['www']
Then, if I enter public.page.com or www.page.com, Apartment won't switch to another tenant, but it will stay at the public one. Of course "public" is not a tenant itself, is just the common data between tenants, so, I don't want any user using the public schema.
What would be the correct way to avoid this?
This app is running on AWS, so, route 53 is going to prevent this, but, although I want to avoid rails from serving request through this subdomain.
Apart from excluding domain from Apartment, you need to exclude them from routes.
In my project I'm using this code for manage this:
I'm using initializer to create array of excleded subdomains.
# config/initializers/apartment/subdomain_exlusions.rb
Apartment::Elevators::Subdomain.excluded_subdomains = ['www', 'admin']
Then, we can use this array in helper class in routes.
# config/routes.rb
class ExcludedSubdomainConstraint
def self.matches?(request)
request.subdomain.present? && !Apartment::Elevators::Subdomain.excluded_subdomains.include?(request.subdomain)
end
end
Rails.application.routes.draw do
constraints ExcludedSubdomainConstraint do
# here routes that are accessible in subdomains
end
end
As a bonus, you can route excluded subdomains to another constrain
class DashboardSubdomainConstraint
def self.matches?(request)
Apartment::Elevators::Subdomain.excluded_subdomains.include?(request.subdomain) || request.subdomain == ''
end
end
constraints DashboardSubdomainConstraint do
namespace :dashboard do
get '/settings'
end
end
will give you a route like www.domain.com/dashboard/settinigs with access to public tenant.
TIP. And you can use different root method in concerns
Related
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
I'm using Rails 5 and Nginx, if that's relevant
I'd like to create a website where a user can be assigned a custom subdomain. Unfortunately, I have no idea how I'd implement that.
Would the best way be using Rails routing? Or should this be an Nginx thing?
Any help is appreciated!
Check https://stackoverflow.com/a/29483146/4515647
Basically:
Validate that the property that will be used as a subdomain (e.g. 'name') is not something like 'www'
Get models (e.g. User) from request.subdomain in controller
Create a Subdomain class like the following that is autoloaded:
:
Class Subdomain
def self.matches?(request)
case request.subdomain
when 'www', '', nil
false
else
true
end
end
end
Configure your routes
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 am pretty new to Rails and most of my knowledge depends on tutorials :)
So, I followed this http://www.railstutorial.org tutorial and created really good site but now I run into a problem. For my users I have a special column in my database which shows which type of user he is. For example I have column 'student' which is 'true' if user is student and 'false' if he's not.
Now I would like to create a subdomain for students. So, when student wants to sign up or sign in he would be transferred to www.student.mysite.com instead of www.mysite.com.
How can I accomplish that?
Thank you :)
There are a number of ways to do this, specifically you'll be interested in looking up multi-tenancy in respect to rails
--
Multi Tenancy
Whilst multi tenancy is typically the definition of having multiple databases / assets (one for each user), however, as it's ridiculously difficult to get this working in rails (something we're currently working on), you can use the principle with a single stack of data
There are several tutorials on how to achieve this with Rails here:
Basecamp-style subdomains by DHH (although looks like the post is down)
Multitenancy with PostgreSQL (Railscasts)
Apartment Gem (achieving multi tenancy on Rails)
Although this is not directly related to your question, most of the "multi tenancy" questions
are typically based on "how do I create different subdomains for my users"
--
Subdomains
The basis of subdomains on Rails is to capture the request, and route it to the correct controller. We've managed to achieve that using the following setup:
#config/routes.rb
constraints Subdomain do #-> lib/subdomain.rb & http://railscasts.com/episodes/221-subdomains-in-rails-3
#Account
namespace :accounts, path: "" do #=> http://[account].domain.com/....
#Index
root to: "application#show"
end
end
#lib/subdomain.rb
class Subdomain
def self.matches?(request)
request.subdomain.present? && request.subdomain != 'www'
end
end
This will give you the ability to do the following:
#app/controllers/accounts/application_controller.rb
class Account::ApplicationController < ActionController::Base
before_action :set_account
def show
##account set before_action. If not found, raises "not found" exception ;)
end
private
#Params from Subdomain
def set_account
params[:id] ||= request.subdomains.first unless request.subdomains.blank?
#account = Account.find params[:id]
end
end
Ideally, we'd love to handle this in the middleware, but as it stands, this is what we've got!
This will give you the ability to call the data you need from the #account variable:
#app/views/accounts/application/show.html.erb
<%= #account.name %>
i have a task to create mapping of different urls at run time .
In the application i have a GUI interface which displays list of routes from routes.rb file.
User has the ability to change that url to some different name from the interface
eg. (abc/mno) --user can change them to --(hello)
so if user type /hello in the browser request is redirected to /abc/mno
i have to store those mapped routes in a database.
how to add a dynamic mapped route to already defined routes(routes.rb) while creating a new record in database
how to add routes from the database while loading routes.rb file.
i am not able to figure out how to extend the default router so that it can include routes from the database ..
I don't have a complete solution for you, but you can start with two approaches:
Use custom URL constraint: Dynamic URL -> Controller mapping for routes in Rails
Use Rack middleware: Dynamic Rails routing based on database
If you don't want to use rack middleware, you can use constraints. Hopefully, your dynamic routes are scoped to something, like "/abc/anything-after-here-can-be-dynamic", as opposed to straight off the root...
So, lets say you wanted dynamic routes based upon User's first name, then you would do the following:
#config/routes.rb
match '/abc/:route' => "abc#dynamicroute", :contraints => DynamicRouteConstraint.new
#lib/dynamic_route_constraint.rb
class DynamicRouteConstraint < Struct.new
def matches?(request)
User.find_by_first_name(request.params[:route]).present?
end
end
#app/controllers/abc_controller.rb
class AbcController < ApplicationController
def dynamicroute
#user = User.find_by_first_name(params[:route])
#render or redirect, however you wish
end
end