Implementing Multi-Tenancy in an `existing` Ruby On Rails (6+, 7+)Applications Using Apartment Gem and Subdomains (Part 2/3)

Hamzawais
3 min readMar 22, 2024

This is the second part of the article. If you want to see what we did earlier, Check part 1.

Installing Apartment Gem and use Multi-schema methodology

Since, apartment (OG) does not support rails 6, we will be using https://github.com/rails-on-services/apartment as a substitue. It provides the exact same API, so kudos to the forkers!

#Gemfile
gem 'ros-apartment', require: 'apartment'
bundle install

Now you should have the gem installed. Onto initializers —

# config/initializers/apartment.rb
require 'apartment/elevators/subdomain'
Apartment.configure do |config|
config.excluded_models = ["Clinic"]
config.tenant_names = lambda { Clinic.pluck(:subdomain) }
end
Rails.application.config.middleware.use Apartment::Elevators::Subdomain
  • Require Apartment Elevator: require 'apartment/elevators/subdomain' loads the Subdomain elevator from the Apartment gem. Elevators in Apartment are middleware components that determine the current tenant based on some aspect of the request, in this case, the subdomain. This allows the application to switch between different tenants (databases or schemas) based on the URL's subdomain.
  • Apartment Configuration Block: Inside the Apartment.configure do |config| block, various Apartment settings are configured:
  • config.excluded_models = ["Clinic"] specifies models that should not be scoped to tenants. These models will remain in the global (public) namespace and are accessible across all tenants. Typically, these are models that contain data relevant to the entire application rather than data specific to a single tenant.
  • config.tenant_names = lambda { Clinic.pluck(:subdomain) } dynamically sets the list of tenant names based on the subdomains stored in the Clinic model. This lambda function is called to generate an array of subdomains, which Apartment uses to identify valid tenants. Whenever a request comes in, Apartment will use this list to determine if the request's subdomain corresponds to a known tenant.
  • Middleware Use: Rails.application.config.middleware.use Apartment::Elevators::Subdomain adds the Apartment Subdomain elevator to the Rails middleware stack. This tells Rails to use this elevator to parse incoming requests and switch to the appropriate tenant based on the subdomain of the request. Essentially, it automates the process of identifying which tenant's data should be accessed for a given request, enabling seamless multi-tenancy support across the application.

Setting Tenant on request

class ApplicationController < ActionController::Base
before_action :switch_tenant

def switch_tenant
# Extract subdomain from request
subdomain = request.subdomain

# Switch to the tenant schema
Apartment::Tenant.switch!(subdomain) if subdomain.present?
end
end

Creating Schemas for new clinics

#app/models/clinic.rb
class Clinic < ApplicationRecord
after_create :create_tenant

validates :subdomain, presence: true, uniqueness: { case_sensitive: false }

def create_tenant
Apartment::Tenant.create(self.subdomain)
command = "DB=#{self.subdomain} rake db:migrate"
system(command)
end
end

The Clinic model represents individual tenants in a multi-tenant application, where each tenant's data is isolated. Upon creating a new clinic, it validates the uniqueness and presence of a subdomain, ensuring each clinic has a distinct URL segment. The after_create :create_tenant callback triggers the create_tenant method post-creation, which performs two main functions:

  1. It calls Apartment::Tenant.create(self.subdomain) to create a new tenant associated with the clinic's subdomain. This step typically involves setting up a new database schema named after the subdomain, isolating the clinic's data.
  2. It executes a command to run database migrations for the new tenant’s schema using system(command), ensuring the schema is up-to-date with the application's database structure.

This setup facilitates data isolation by dynamically managing database schemas for each clinic, leveraging the Apartment gem for efficient multi-tenancy.

Creating Schemas for Old clinics

# rake task or anything else

namespace :tenant_data_migration do
desc "Migrate data to tenant schemas"
task migrate: :environment do
Rails.application.eager_load!

Clinic.find_each do |clinic|
Apartment::Tenant.drop(clinic.subdomain)
Apartment::Tenant.create(clinic.subdomain)
command = "DB=#{clinic.subdomain} rake db:migrate"
system(command)
p "Migrated schema for #{clinic.subdomain}!"
end

end

end

By now you should have everything set up and your multi-tenancy using multi-schema and subdomains should be up and running!

If you want to see how we can update sidekiq, and action cable / stimulus reflex to leverage apartment gem — next part would be for you.

You get in touch with me:

email: hamzawais54@gmail.com | Phone: +923244105651 | web: meet-hamza.com | linkedin: https://www.linkedin.com/in/hamza-awais-1908/

--

--