-
Notifications
You must be signed in to change notification settings - Fork 158
Tenant Switching
To switch tenants using Apartment, use the following command:
Apartment::Tenant.switch('tenant_name') do
# ...
end
When switch is called, all requests coming to ActiveRecord will be routed to the tenant you specify (with the exception of excluded models, see below). The tenant is automatically switched back at the end of the block to what it was before.
There is also switch!
which doesn't take a block, but it's recommended to use switch
.
To return to the default tenant, you can call switch
with no arguments.
You can have Apartment route to the appropriate tenant by adding some Rack middleware. Apartment can support many different "Elevators" that can take care of this routing to your data.
NOTE: when switching tenants per-request, keep in mind that the order of your Rack middleware is important. See the Middleware Considerations section for more.
The initializer above will generate the appropriate code for the Subdomain elevator
by default. You can see this in config/initializers/apartment.rb
after running
that generator. If you're not using the generator, you can specify your
elevator below. Note that in this case you will need to require the elevator
manually in your application.rb
like so
# config/application.rb
require 'apartment/elevators/subdomain' # or 'domain', 'first_subdomain', 'host'
In house, we use the subdomain elevator, which analyzes the subdomain of the request and switches to a tenant schema of the same name. It can be used like so:
# application.rb
module MyApplication
class Application < Rails::Application
config.middleware.use Apartment::Elevators::Subdomain
end
end
If you want to exclude a domain, for example if you don't want your application to treat www like a subdomain, in an initializer in your application, you can set the following:
# config/initializers/apartment/subdomain_exclusions.rb
Apartment::Elevators::Subdomain.excluded_subdomains = ['www']
This functions much in the same way as Apartment.excluded_models. This example will prevent switching your tenant when the subdomain is www. Handy for subdomains like: "public", "www", and "admin" :)
To switch on the first subdomain, which analyzes the chain of subdomains of the request and switches to a tenant schema of the first name in the chain (e.g. owls.birds.animals.com would switch to "owls"). It can be used like so:
# application.rb
module MyApplication
class Application < Rails::Application
config.middleware.use Apartment::Elevators::FirstSubdomain
end
end
If you want to exclude a domain, for example if you don't want your application to treat www like a subdomain, in an initializer in your application, you can set the following:
# config/initializers/apartment/subdomain_exclusions.rb
Apartment::Elevators::FirstSubdomain.excluded_subdomains = ['www']
This functions much in the same way as the Subdomain elevator. NOTE: in fact, at the time of this writing, the Subdomain
and FirstSubdomain
elevators both use the first subdomain (#339). If you need to switch on larger parts of a Subdomain, consider using a Custom Elevator.
To switch based on full domain (excluding the 'www' subdomains and top level domains ie '.com' ) use the following:
# application.rb
module MyApplication
class Application < Rails::Application
config.middleware.use Apartment::Elevators::Domain
end
end
Note that if you have several subdomains, then it will match on the first non-www subdomain:
- example.com => example
- www.example.com => example
- a.example.com => a
To switch based on full host with a hash to find corresponding tenant name use the following:
# application.rb
module MyApplication
class Application < Rails::Application
config.middleware.use Apartment::Elevators::HostHash, {'example.com' => 'example_tenant'}
end
end
To switch based on full host to find corresponding tenant name use the following:
# application.rb
module MyApplication
class Application < Rails::Application
config.middleware.use Apartment::Elevators::Host
end
end
If you want to exclude a first-subdomain, for example if you don't want your application to include www in the matching, in an initializer in your application, you can set the following:
Apartment::Elevators::Host.ignored_first_subdomains = ['www']
With the above set, these would be the results:
- example.com => example.com
- www.example.com => example.com
- a.example.com => a.example.com
- www.a.example.com => a.example.com
A Generic Elevator exists that allows you to pass a Proc
(or anything that responds to call
) to the middleware. This Object will be passed in an ActionDispatch::Request
object when called for you to do your magic. Apartment will use the return value of this proc to switch to the appropriate tenant. Use like so:
# application.rb
module MyApplication
class Application < Rails::Application
# Obviously not a contrived example
config.middleware.use Apartment::Elevators::Generic, proc { |request| request.host.reverse }
end
end
Your other option is to subclass the Generic elevator and implement your own
switching mechanism. This is exactly how the other elevators work. Look at
the subdomain.rb
elevator to get an idea of how this should work. Basically
all you need to do is subclass the generic elevator and implement your own
parse_tenant_name
method that will ultimately return the name of the tenant
based on the request being made. It could look something like this:
# app/middleware/my_custom_elevator.rb
class MyCustomElevator < Apartment::Elevators::Generic
# @return {String} - The tenant to switch to
def parse_tenant_name(request)
# request is an instance of Rack::Request
# example: look up some tenant from the db based on this request
tenant_name = SomeModel.from_request(request)
return tenant_name
end
end
In the examples above, we show the Apartment middleware being appended to the Rack stack with
Rails.application.config.middleware.use Apartment::Elevators::Subdomain
By default, the Subdomain middleware switches into a Tenant based on the subdomain at the beginning of the request, and when the request is finished, it switches back to the "public" Tenant. This happens in the Generic elevator, so all elevators that inherit from this elevator will operate as such.
It's also good to note that Apartment switches back to the "public" tenant any time an error is raised in your application.
This works okay for simple applications, but it's important to consider that you may want to maintain the "selected" tenant through different parts of the Rack application stack. For example, the Devise gem adds the Warden::Manager
middleware at the end of the stack in the examples above, our Apartment::Elevators::Subdomain
middleware would come after it. Trouble is, Apartment resets the selected tenant after the request is finish, so some redirects (e.g. authentication) in Devise will be run in the context of the "public" tenant. The same issue would also effect a gem such as the better_errors gem which inserts a middleware quite early in the Rails middleware stack.
To resolve this issue, consider adding the Apartment middleware at a location in the Rack stack that makes sense for your needs, e.g.:
Rails.application.config.middleware.insert_before Warden::Manager, Apartment::Elevators::Subdomain
Now work done in the Warden middleware is wrapped in the Apartment::Tenant.switch
context started in the Generic elevator.