🎸 Building a Multi-Tenant Rails App for Music Stores Using Apartment

May 14, 2025

A few months ago, I had to build a SaaS platform for music instrument stores. Each store needed to manage its own products, customers, and sales, but everything had to run under one codebase.

Article content

I ended up going with the apartment gem to handle multi-tenancy — specifically, schema-based separation using PostgreSQL. It was a learning curve, but totally worth it. Here’s how I made it work and some things I learned along the way.


🎯 Live Demo Available
Introducing

MapView

Render beautiful, production-ready maps directly from your Ruby backend. No external APIs. No dependencies. Just pure speed and control.

Zero external dependencies
Lightning-fast rendering
Production-ready & battle-tested

The Setup

The idea was simple: every store that signs up gets its own schema. That way, their data stays isolated, and they don’t accidentally see another store’s inventory or customers.

I added apartment to the Gemfile:

gem 'apartment'

Then configured it like this:

# config/initializers/apartment.rb

Apartment.configure do |config|
  config.excluded_models = %w[User] # Global models like admin users
  config.use_schemas = true
end

# This switches tenants based on subdomain (e.g. store1.myapp.com)
Rails.application.config.middleware.use Apartment::Elevators::Subdomain

This setup assumes each store logs in via their own subdomain, like rockstore.myapp.com or jazzcenter.myapp.com.


Creating Stores (Tenants)

Article content

Once a store signs up, I just run:

Apartment::Tenant.create("rockstore")

That creates a new schema inside PostgreSQL. Then I seed it with default data or let them start fresh.

For dev and testing, I used something like this in the Rails console:

Apartment::Tenant.switch("rockstore") do
  Instrument.create(name: "Fender Jazz Bass", brand: "Fender", price: 950)
end

Each store gets their own instruments, customers, and orders — completely separate from the others.


A Bit About Models

I kept it pretty simple:

rails generate model Instrument name:string brand:string price:decimal
rails generate model Customer name:string email:string
rails generate model Order customer:references total:decimal

These models live in the tenant schemas. Global stuff (like platform admins) stays in the public schema — that’s what the excluded_models setting is for.


Running Migrations

One thing to watch out for: every time you change the schema, you’ll want to run the migrations across all tenants. The gem gives you a rake task for that:

rake apartment:migrate

If you forget this, some tenants will have outdated table structures — been there, done that 😅.


What Worked Well

  • I liked how clean and isolated the data was per store.
  • Switching schemas based on subdomain worked great.
  • Onboarding new stores was simple — just Apartment::Tenant.create(…) and you’re good to go.

A Few Gotchas

  • Migrations can be tricky. You’ll need to be careful testing them in multiple tenants.
  • Avoid jumping between tenants too much inside the same thread or request — always make sure you reset.
  • It’s not the best fit if you’re managing thousands of tenants — PostgreSQL has limits on schema counts, and you might hit performance issues eventually.

Final Thoughts

If you’re building a multi-tenant Rails app and want strong data isolation, apartment is worth considering — especially if you’re already using PostgreSQL.

It saved me a ton of work and let me keep one codebase while still giving each music store their own “space” to operate in. Just be ready to get your hands a little dirty with migrations and schema management.

If you’re trying it out and hit a wall, feel free to reach out. I probably ran into the same thing at some point.

Article content

Leave a comment