# Multitenancy
> Multitenancy is an architecture where one application serves many customers, called tenants, and each tenant sees only its own data. In Rails it is built with a tenant column on every row, a database schema per tenant, or a database per tenant.

Multitenancy is an architecture where one application serves many customers, called tenants, and each tenant sees only its own data. A tenant is usually a company, team or account, not a single user: several people log in to the same tenant and share its records. The code, the servers and often the database are shared. The data is not.

The design question is where the wall between tenants lives: in your queries, in the database's namespaces, or in separate databases.

## Three ways to build it in Rails

**Row-level: a tenant column on every table.** Every tenant-owned table gets a column such as `account_id`, and every query filters on it.

- Cheapest to run. One migration path, one connection pool, normal indexes.
- Cross-tenant reporting is a plain query.
- The wall is in application code. Forget the filter once and a tenant sees someone else's rows.

**Schema per tenant.** On PostgreSQL, each tenant gets its own schema, a namespace holding a full copy of your tables. The app switches the connection's search path per request.

- A missing `where` cannot cross tenants, because the other tenant's tables are not in the search path.
- Every migration runs once per tenant, so deploys get slower as tenants grow.
- Cross-tenant queries need you to name each schema.

**Database per tenant.** Each tenant gets a separate database, sometimes on a separate server.

- The strongest isolation. You can back up, restore, move or delete one tenant on its own.
- The most to operate: connections, migrations and monitoring multiply by the tenant count.
- It suits a few large customers with contractual isolation needs, not thousands of small ones.

Apartment's own README recommends row-level tenancy for greenfield projects and apps with many tenants. Schema and database per tenant earn their cost when a customer contract or regulator asks for physical separation.

## Row-level with acts_as_tenant

[acts_as_tenant](https://github.com/ErwinM/acts_as_tenant) is the common gem for row-level tenancy. Its latest release is 2.2.2, published in October 2026 (checked in November 2026).

```ruby
# Gemfile
gem "acts_as_tenant"
```

Add the tenant column, then declare it on the model. `acts_as_tenant :account` adds the `belongs_to :account` for you, so do not declare both.

```ruby
# bin/rails g migration AddAccountToProjects account:references
class Project < ApplicationRecord
  acts_as_tenant :account
  validates_uniqueness_to_tenant :name
end
```

Set the current tenant once per request. The gem ships helpers for subdomains, or you set it yourself from the signed-in user.

```ruby
class ApplicationController < ActionController::Base
  set_current_tenant_through_filter
  before_action :set_tenant

  private

  def set_tenant
    set_current_tenant(current_user.account)
  end
end
```

From then on `Project.all` returns only the current account's projects, and `Project.new` fills in `account_id`. The scoping comes from a `default_scope`, so it applies to every query built from the model, including `update_all` and `destroy_all`. `validates_uniqueness_to_tenant` lets two tenants each have a project called "Website". The [models](https://avohq.io/glossary/models) stay ordinary Active Record classes.

## Schema per tenant with Apartment

For schema per tenant, the usual gem is Apartment. Its README describes the `ros-apartment` gem as a maintained fork of the original. The stable release is 3.4.4, and a 4.0 line is in alpha. The core API is a block that switches tenant:

```ruby
Apartment::Tenant.create("acme")

Apartment::Tenant.switch("acme") do
  Project.create!(name: "Website") # written to the acme schema
end
```

## What Rails itself offers

The [multiple databases guide](https://guides.rubyonrails.org/active_record_multiple_databases.html) covers horizontal sharding, which it describes as commonly called multi-tenant sharding. You declare shards with `connects_to` and switch with `connected_to`:

```ruby
class ShardRecord < ApplicationRecord
  self.abstract_class = true

  connects_to shards: {
    shard_one: { writing: :primary_shard_one },
    shard_two: { writing: :primary_shard_two }
  }
end

class Person < ShardRecord
end

ShardRecord.connected_to(role: :writing, shard: :shard_one) do
  Person.create!
end
```

The guide also documents a `ShardSelector` middleware that picks the shard per request from a resolver you write, and says `lock` should stay `true` for tenant-based sharding so code cannot switch tenants mid-request.

```ruby
# config/initializers/multi_db.rb, generated by bin/rails g active_record:multi_db
Rails.application.configure do
  config.active_record.shard_selector = { lock: true }
  config.active_record.shard_resolver = ->(request) { Tenant.find_by!(host: request.host).shard }
end
```

Each shard is its own entry in `config/database.yml`, declared on the model with `connects_to`, so this is database per tenant with Rails' own tools.

## How data leaks across tenants

Row-level tenancy fails open. When no tenant is set, acts_as_tenant applies no scope, and `require_tenant` is `false` by default. Leaks come from three places:

1. **Unscoped queries.** `unscoped` removes the tenant scope along with the one you meant to drop. Raw SQL (`find_by_sql`, `connection.execute`) skips it too, and `update_columns` skips the check that stops a record moving to another tenant.
2. **Background jobs.** Jobs run outside the request, so the controller never set a tenant. acts_as_tenant stores the tenant with ActiveJob jobs and restores it when they run. Sidekiq needs `require "acts_as_tenant/sidekiq"` in an initializer.
3. **The console.** `bin/rails console` starts with no tenant, so `Project.all` returns every tenant's projects.

Turn on `require_tenant` so a missing tenant raises instead of returning everything:

```ruby
# config/initializers/acts_as_tenant.rb
ActsAsTenant.configure do |config|
  config.require_tenant = true
end
```

Queries without a tenant now raise `ActsAsTenant::Errors::NoTenantSet`. For code that should see every tenant, wrap it in `ActsAsTenant.without_tenant { ... }` so the exception is visible in review.

## Testing for leaks

Write a request spec with two tenants and assert that one cannot load the other's record:

```ruby
RSpec.describe "Projects", type: :request do
  it "does not show another account's project" do
    mine = create(:account)
    theirs = create(:account)
    project = ActsAsTenant.with_tenant(theirs) { create(:project) }

    sign_in create(:user, account: mine)
    get project_path(project)

    expect(response).to have_http_status(:not_found)
  end
end
```

Cover every resource a tenant can reach, including JSON endpoints and exports. The 404 comes from `find` raising `ActiveRecord::RecordNotFound`; if your test environment re-raises exceptions, assert that instead. Reset `ActsAsTenant.current_tenant` after each test, as the README shows, so one test's tenant does not mask a leak in the next.

## Tenancy is not permissions

Tenancy decides which tenant's rows are in scope; who may do what inside them is a separate layer, covered by [role-based access control](https://avohq.io/glossary/role-based-access-control) and [record-level permissions](https://avohq.io/glossary/record-level-permissions).

## Multitenancy in an Avo app

If your team builds its admin with [Avo](https://avohq.io/rails-admin), the [multitenancy docs](https://docs.avohq.io/4.0/multitenancy.html) show how to tell it which tenant a request is for. `Avo::Current` has `tenant_id` and `tenant` attributes that you set in a `before_action`. The page gives two recipes: route-based, with Avo mounted under `/:tenant_id`, and session-based, with an account switcher.

## Frequently asked questions

### What is multitenancy in simple terms?

Multitenancy means one application serves many customers, and each customer sees only its own data. The customers, called tenants, share the code and servers but not the records. A tenant is usually a company or team, so several users share one tenant.

### Should I use row-level, schema or database per tenant in Rails?

Start with row-level tenancy, a tenant column on every table, unless a customer or regulator requires physical separation. It is the cheapest to run and makes cross-tenant reporting a normal query. Schema per tenant and database per tenant isolate data in the database itself, at the cost of migrations and operations that grow with the tenant count.

### What is the difference between acts_as_tenant and Apartment?

acts_as_tenant does row-level tenancy: every tenant shares the same tables and each query is filtered by a tenant column. Apartment does schema per tenant on PostgreSQL, or database per tenant, and switches the connection between them. Choose the isolation model first and the gem follows.

### How do I stop data leaking between tenants?

Make a missing tenant an error, for example with acts_as_tenant's `require_tenant` option, so queries cannot run unscoped by accident. Check background jobs, console sessions and raw SQL, since those run outside the normal request scoping. Then write request specs with two tenants that assert one cannot load the other's records.

### Can an admin see data across all tenants?

Only if you build it that way. With acts_as_tenant, staff tools can run inside `without_tenant` or be exempted from `require_tenant` by path. Keep that access separate from tenant users and limit it with roles and permissions.
