Hierarchy-aware, opt-in multi-tenancy for ActiveRecord.
- Declare a tenant root with
sublet
. - Declare subletters with
subletter(:parent_assoc)
— the parent can be the tenant or another subletter. - You set
Sublet.current_tenant
yourself (only in the controllers you want). - Sublet auto-scopes queries via
default_scope
so admin filters, counts, etc. follow the tenant. - Fails open when tenant chains can't be resolved (no scoping applied).
- Includes automatic validation to prevent tenant mismatches.
Add this line to your application's Gemfile:
gem "sublet", "~> 0.1.0"
And then execute:
bundle install
Or install it yourself as:
gem install sublet
- Ruby >= 3.1.0
- ActiveRecord >= 6.1
- ActiveSupport >= 6.1
class Company < ApplicationRecord
include Sublet::Model
sublet
end
class Department < ApplicationRecord
include Sublet::Model
subletter(:company)
belongs_to :company
end
class Employee < ApplicationRecord
include Sublet::Model
subletter(:department)
belongs_to :department
has_one :company, through: :department
end
In a controller where you want tenant scoping:
class Admin::BaseController < ApplicationController
before_action do
Sublet.current_tenant = current_user.company
end
end
Or use the included controller helper:
class Admin::BaseController < ApplicationController
before_action do
set_current_sublet(current_user.company)
end
end
# Temporarily switch to a different tenant
Sublet.with_tenant(Company.find(1)) do
Employee.count # Scoped to Company 1
end
# Temporarily bypass tenant scoping
Sublet.without_tenant do
Employee.unscoped.count # All employees across all tenants
end
class Account < ApplicationRecord
include Sublet::Model
sublet
end
class Company < ApplicationRecord
include Sublet::Model
subletter(:account)
belongs_to :account
end
class Department < ApplicationRecord
include Sublet::Model
subletter(:company)
belongs_to :company
end
class Employee < ApplicationRecord
include Sublet::Model
subletter(:department)
belongs_to :department
end
class Company < ApplicationRecord
include Sublet::Model
sublet(scope: false) # Declare as tenant but don't auto-scope
end
Sublet automatically validates that records belong to the current tenant:
# This will fail validation if the employee doesn't belong to current_tenant
employee = Employee.new(department: wrong_department)
employee.valid? # => false
employee.errors[:base] # => ["Tenant mismatch"]
- Hierarchy-aware: Supports multi-level tenant hierarchies (Account → Company → Department → Employee)
- Opt-in: Only applies scoping where you explicitly set
current_tenant
- Fails open: If tenant chain can't be resolved, no scoping is applied (safe default)
- Automatic validation: Prevents tenant mismatches during record creation/updates
- Controller helpers: Includes
set_current_sublet
helper method - Temporary switching:
with_tenant
andwithout_tenant
for temporary context changes - STI-safe: Works correctly with Single Table Inheritance
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and the created tag, and push the .gem
file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/gogrow-dev/sublet. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
The gem is available as open source under the terms of the MIT License.
- If a subletter chain can't be resolved to a tenant, Sublet fails open (no scoping).
- Tenants themselves are not scoped by default (use
sublet(scope: true)
to enable). - The gem automatically includes controller helpers when Rails is present.