Acts As Tenant Integration
Recipe contributed by SahSantoshh.
WARNING
The guide expressed here shows how you we can add subdomain-level multitenancy (sah.example.org, adrian.example.org, etc).
This makes for more than one URL per application which in turn requires a special license. To get more information please reach out to us.
There are different ways to achieve multi-tenancy in an application. We already have a doc which describes about Multitenancy with Avo. Here we will deep dive in integrating Acts As Tenant which supports row-level multitenancy with Avo. In this implementation we will be setting tenant to subdomain.
INFO
Check out the acts_as_tenant documentation for reference.
Installation
To use it, add it to your Gemfile:
gem 'acts_as_tenant'
Tenant
Let's create model for tenant. We are using Account
as our tenant.
Account Migration and Model class
# Migration
class CreateAccounts < ActiveRecord::Migration[7.1]
def change
create_table :accounts do |t|
t.string :name
t.string :subdomain
t.timestamps
end
add_index :accounts, :subdomain, unique: true
add_index :accounts, :created_at
end
end
# Account model handles Tenant management
class Account < ApplicationRecord
MAX_SUBDOMAIN_LENGTH = 20
validates :name, :subdomain, presence: true
validates_uniqueness_of :name, :subdomain, case_sensitive: false
validates_length_of :subdomain, :name, maximum: MAX_SUBDOMAIN_LENGTH
end
Scope models
Now let's add users to Account
. Here I am assuming to have an existing user model which is used for Authentication
. Similarly we can scope other models.
class AddAccountToUsers < ActiveRecord::Migration
def up
add_column :users, :account_id, :integer # if we have existing user set null to true then update the data using seed
add_index :users, :account_id
end
end
# Authentication
class User < ActiveRecord::Base
acts_as_tenant(:account)
end
Setting the current tenant
There are three ways to set the current tenant but we be using the subdomain to lookup the current tenant. Since Avo has it's own Application Controller
so there is no point in setting the tenant in Rails default Application Controller
but we will set it there as well just to be safe site and also we might have some other pages other than Admin Dashboard supported by Avo.
# Multitenancy, to set the current account/tenant.
module Multitenancy
extend ActiveSupport::Concern
included do
prepend_before_action :set_current_account
end
def set_current_account
hosts = request.host.split('.')
# just to make sure we are using subdomain path
subdomain = (hosts[0] if hosts.length > 2)
# We only allow users to login from their account specific subdomain not outside of it.
sign_out(current_user) if subdomain.blank?
current_account = Account.find_by(subdomain:)
sign_out(current_user) if current_account.blank?
# set tenant for Avo and ActAsTenant
ActsAsTenant.current_tenant = current_account
Avo::Current.tenant = current_account
Avo::Current.tenant_id = current_account.id
end
end
Avo.configure do |config|
# configuration values
end
Rails.configuration.to_prepare do
Avo::ApplicationController.include Multitenancy
end
Now, whenever we navigate to https://sahsantoshh.example.com/ the tenant & the tenant_id will be set to sahsantoshh.
Move existing data to model
We might have to many users and other records which needs to be associated with Account
. For example, we will only move users record to the account
# Create default/first account where we want to associate exiting data
account = Account.find_or_create_by!(name: 'Nepal', subdomain: 'sahsantoshh')
User.unscoped.in_batches do |relation|
relation.update_all(account_id: account.id)
sleep(0.01) # throttle
end
Now run the seed command to update existing records