Using Apartment for multi-tenant Rails apps
This is a step-by-step guide to setting up the Apartment gem for Ruby on Rails. Apartment greatly simplifies the technical challenges of creating multi-tenant applications. That being said, there's still a bit of setup you'll need to do, and this guide will help with that.
We'll create an application with Tenants (in other words, users) who have their own scoped Projects (for the purposes of this post, just a generic resource with a title). Each tenant will have their own subdomain. This kind of structure is similar to Slack: companies can sign up for theirsubdomain.yoursite.com and create resources isolated to their instance.
-
To start, we'll spin up a new Rails application and scaffold
Tenants
andProjects
.rails new multitenant cd multitenant rails g scaffold Tenants name email subdomain rails g scaffold Projects title rails db:migrate
-
Now, let's install Apartment. Add
gem 'apartment'
to your Gemfile. -
Run
bundle
. -
Run
rails g apartment:install
. -
Next, let's configure Apartment. In config/initializers/apartment.rb, find the line that says:
config.tenant_names = lambda { ToDo_Tenant_Or_User_Model.pluck :database }
And change it to:
config.tenant_names = lambda { Tenant.pluck :subdomain }
-
In the same file, uncomment the line for
excluded_models
.config.excluded_models = %w{ Tenant }
-
Now, let's set up subdomains on account creation. Change your Tenant model to create a new subdomain when a tenant is created.
# tenant.rb # Before class Tenant < ApplicationRecord end # After class Tenant < ApplicationRecord after_create :create_tenant private def create_tenant Apartment::Tenant.create(subdomain) end end
-
Next, it's time to spin up our multi-tenant development server. We're almost ready to test out subdomains. However, since http://localhost:3000 isn't a real domain, we can't use subdomains like http://subdomain.localhost:3000. Thankfully, someone set up lvh.me for this purpose. It points back to your local machine so you can test subdomains! You'll need to add some parameters to
rails s
to achieve this:rails s -p 3000 -b lvh.me
-
Now we'll create our first tenants. Start your server with the command from the previous step. Create two tenants with the subdomains
zeph
andasdf
. I'm doing this on /tenants/new, which I scaffolded in Step 1. (If you didn't scaffold, make sure your tenants have a subdomain field on their signup form. This hooks into the model we edited in Step 7. If you're using Devise, you should add a custom field to your registration form.) -
Go to zeph.lvh.me:3000 and check out your first subdomain!
-
It's time to create our first scoped projects. Go to zeph.lvh.me:3000/projects/new and create a new project. (We scaffolded Projects in Step 1.) The database scoping happens behind the scenes thanks to Apartment, so projects created on zeph won't appear on asdf. Nice job!
-
Next, we want to differentiate the root domain. In most cases, you won't want your root domain to run the same application as your subdomains. Instead, you probably want to use it as a marketing site with a Home, About, Sign Up, etc. page.
We can achieve this by placing the marketing site on the www subdomain, which is highly recommended for multi-tenant applications. When you're in production, you'll want to redirect your non-www subdomain to www.
Add a new folder inside config/initializers called apartment. Inside that, create a new file called subdomain_exclusions.rb and add the following:
# subdomain_exclusions.rb Apartment::Elevators::Subdomain.excluded_subdomains = ['www']
Restart your server, and you'll be able to access www.lvh.me:3000.
-
Finally, we want to block access to a few resources depending on which part of the application the user is browsing. We should disable /projects on our marketing site and disable /tenants on our users' subdomains. Let's add subdomain constraints in routes.rb:
# routes.rb class SubdomainConstraint def self.matches? request subdomains = %w{ www } request.subdomain.present? && !subdomains.include?(request.subdomain) end end Rails.application.routes.draw do resources :tenants, constraints: { subdomain: 'www' } constraints SubdomainConstraint do resources :projects end end
Now, you can only access /projects from a tenant subdomain. And you can't register a new tenant unless you're on the
www
site.In the future, when you need to add new resources to your application, pay special attention to this routing file. Make sure that you can't access a tenant-only resource from your marketing site and vice versa by following the same method that we used above.
Great job! You've now created a multi-tenant application. Only a small amount of people in the world ever have, so you should be feeling pretty good about yourself.
Apartment has a lot of moving pieces, and I felt that this guide would be helpful for people who wanted to get started on integrating it in their Rails 5.2 apps. If you enjoyed it, please get my face tattooed on your forehead.