Apartment
Database multi-tenancy for Rack (and Rails) applications
Install / Use
/learn @influitive/ApartmentREADME
Apartment
Multitenancy for Rails and ActiveRecord
Apartment provides tools to help you deal with multiple tenants in your Rails application. If you need to have certain data sequestered based on account or company, but still allow some data to exist in a common tenant, Apartment can help.
HELP!
In order to help drive the direction of development and clean up the codebase, we'd like to take a poll on how people are currently using Apartment. If you can take 5 seconds (1 question) to answer this poll, we'd greatly appreciated it.
Excessive Memory Issues on ActiveRecord 4.x
If you're noticing ever growing memory issues (ie growing with each tenant you add) when using Apartment, that's because there's an issue with how ActiveRecord maps Postgresql data types into AR data types. This has been patched and will be released for AR 4.2.2. It's apparently hard to backport to 4.1 unfortunately. If you're noticing high memory usage from ActiveRecord with Apartment please upgrade.
gem 'rails', '4.2.1', github: 'influitive/rails', tag: 'v4.2.1.memfix'
Installation
Rails
Add the following to your Gemfile:
gem 'apartment'
Then generate your Apartment config file using
bundle exec rails generate apartment:install
This will create a config/initializers/apartment.rb initializer file.
Configure as needed using the docs below.
That's all you need to set up the Apartment libraries. If you want to switch tenants on a per-user basis, look under "Usage - Switching tenants per request", below.
NOTE: If using postgresql schemas you must use:
- for Rails 3.1.x: Rails ~> 3.1.2, it contains a patch that makes prepared statements work with multiple schemas
Usage
Video Tutorial
How to separate your application data into different accounts or companies. GoRails #47
Creating new Tenants
Before you can switch to a new apartment tenant, you will need to create it. Whenever you need to create a new tenant, you can run the following command:
Apartment::Tenant.create('tenant_name')
If you're using the prepend environment config option or you AREN'T using Postgresql Schemas, this will create a tenant in the following format: "#{environment}_tenant_name". In the case of a sqlite database, this will be created in your 'db/' folder. With other databases, the tenant will be created as a new DB within the system.
When you create a new tenant, all migrations will be run against that tenant, so it will be up to date when create returns.
Notes on PostgreSQL
PostgreSQL works slightly differently than other databases when creating a new tenant. If you are using PostgreSQL, Apartment by default will set up a new schema and migrate into there. This provides better performance, and allows Apartment to work on systems like Heroku, which would not allow a full new database to be created.
One can optionally use the full database creation instead if they want, though this is not recommended
Switching Tenants
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.
Switching Tenants per request
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'
Switch on subdomain
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" :)
Switch on first subdomain
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.
Switch on domain
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
Switch on full host using a hash
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
Switch on full host, ignoring given first subdomains
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
Custom Elevator
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.new { |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 Rac
Related Skills
feishu-drive
343.1k|
things-mac
343.1kManage Things 3 via the `things` CLI on macOS (add/update projects+todos via URL scheme; read/search/list from the local Things database)
clawhub
343.1kUse the ClawHub CLI to search, install, update, and publish agent skills from clawhub.com
codebase-memory-mcp
1.1kHigh-performance code intelligence MCP server. Indexes codebases into a persistent knowledge graph — average repo in milliseconds. 66 languages, sub-ms queries, 99% fewer tokens. Single static binary, zero dependencies.
