Skip to content

Commit 5564413

Browse files
committed
config.active_record_tenanted.connection_class
is a config option that takes a class name (default "ApplicationRecord") that this gem will integrate into Rails test setup and Record classes (to share a connection pool with that connection class and create/use tables in that class's database). Or set to `nil` to turn off integrations. Also introduce config.active_record_tenanted.tenant_rails_records which defaults to `true` but can be set to `false` to leave Record classes in an untenanted primary database. Also added an `after_initialize` block into the railtie to apply the connection class configs.
1 parent 1d7682f commit 5564413

File tree

4 files changed

+71
-14
lines changed

4 files changed

+71
-14
lines changed

GUIDE.md

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Documentation outline:
4848
- demonstrate how to configure an app for subdomain tenants
4949
- app.config.hosts
5050
- example TenantSelector proc
51+
- explain Tenanted options "connection_class" and "tenanted_rails_records"
5152

5253
TODO:
5354

@@ -117,22 +118,17 @@ TODO:
117118
- [ ] install a variation on the default database.yml with primary tenanted and non-primary "global" untenanted
118119
- initializer
119120
- [ ] install `TenantSelector` and configure it with a proc
120-
- [ ] commented line like `Tenant = ActiveRecord::Tenanted::Tenant`
121+
- [ ] commented line like `self.connection_class = "ApplicationRecord"`
122+
- [ ] commented line like `self.tenanted_rails_records = true`
121123

122124
- pruning connections and connection pools
123125
- [ ] look into whether the proposed Reaper changes will allow us to set appropriate connection min/max/timeouts
124126
- and if not, figure out how to prune unused/timed-out connections
125127
- [ ] we should also look into how to cap the number of connection pools, and prune them
126128

127129
- autoloading and configuration hooks
128-
- [ ] create a zeitwerk loader
129-
- [ ] install some load hooks (where?)
130-
131-
- design thinking re: global singleton (I think I've decided to not have one in this iteration)
132-
- should there be a global singleton `Tenant`? I'm not sure we need it or the limitations of a global.
133-
- if we do, though, then `.tenanted` should set `Tenant.base_class=`
134-
- and we need to add checks that `.tenanted` is called only ONCE in the application
135-
- `.tenanted_class` returns nil or the abstract base class. do we need this? let's wait and see.
130+
- [x] create a zeitwerk loader
131+
- [x] install a load hook
136132

137133
- test coverage
138134
- [x] need more complete coverage on `Tenant.ensure_schema_migrations`
@@ -142,8 +138,9 @@ TODO:
142138

143139
Documentation outline:
144140

145-
- introduce the `Tenant` module
141+
- introduce the `ActiveRecord::Tenanted::Tenant` module
146142
- demonstrate how to create a tenant, destroy a tenant, etc.
143+
- explain `.while_tenanted` and `current_tenant`
147144
- troubleshooting: what errors you might see in your app and how to deal with it
148145
- specifically when running untenanted
149146

@@ -159,8 +156,8 @@ Documentation outline:
159156
TODO:
160157

161158
- testing
162-
- [ ] set up test helper to default to a tenanted named "test-tenant"
163-
- [ ] set up test helpers to deal with parallelized tests, too (e.g. "test-tenant-19")
159+
- [x] set up test helper to default to a tenanted named "test-tenant"
160+
- [x] set up test helpers to deal with parallelized tests, too (e.g. "test-tenant-19")
164161
- [ ] allow the creation of tenants within transactional tests if we can?
165162
- either by cleaning up properly (hard)
166163
- or by providing a test helper that does `ensure ... Tenant.destroy`

lib/active_record/tenanted.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,5 @@ class NoTenantError < Error; end
1717
end
1818

1919
loader.eager_load
20+
21+
ActiveSupport.run_load_hooks :active_record_tenanted, ActiveRecord::Tenanted

lib/active_record/tenanted/railtie.rb

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,27 @@
33
module ActiveRecord
44
module Tenanted
55
class Railtie < ::Rails::Railtie
6+
config.active_record_tenanted = ActiveSupport::OrderedOptions.new
7+
8+
# Set this in an initializer if you're tenanting a connection class other than
9+
# ApplicationRecord. This value controls how Rails integrates with your tenanted application.
10+
#
11+
# By default, Rails will configure the test database, test fixtures to use
12+
# "ApplicationRecord", but this can be set to `nil` to turn off the integrations entirely,
13+
# including Rails records (see `tenanted_rails_records` below).
14+
config.active_record_tenanted.connection_class = "ApplicationRecord"
15+
16+
# Set this to false in an initializer if you don't want Rails records to share a connection
17+
# pool with the tenanted connection class.
18+
#
19+
# By default, this gem will configure ActionMailbox::Record, ActiveStorage::Record, and
20+
# ActionText::Record to create/use tables in the database associated with the
21+
# `connection_class`, and will share a connection pool with that class.
22+
#
23+
# This should only be turned off if your primary database configuration is not tenanted, and
24+
# that is where you want Rails to create the tables for these records.
25+
config.active_record_tenanted.tenanted_rails_records = true
26+
627
config.before_configuration do
728
ActiveSupport.on_load(:active_record) do
829
ActiveRecord::DatabaseConfigurations.register_db_config_handler do |env_name, name, _, config|
@@ -12,13 +33,13 @@ class Railtie < ::Rails::Railtie
1233
end
1334
end
1435

15-
initializer "active_record_tenanted.base_records" do
36+
initializer "active_record_tenanted.active_record_base" do
1637
ActiveSupport.on_load(:active_record) do
1738
prepend ActiveRecord::Tenanted::Base
1839
end
1940
end
2041

21-
initializer "active_record-tenanted.monkey_patches.active_record" do
42+
initializer "active_record-tenanted.monkey_patches" do
2243
ActiveSupport.on_load(:active_record) do
2344
# require "rails/generators/active_record/migration.rb"
2445
# ActiveRecord::Generators::Migration.prepend(ActiveRecord::Tenanted::Patches::Migration)
@@ -29,6 +50,40 @@ class Railtie < ::Rails::Railtie
2950
include(ActiveRecord::Tenanted::Patches::TestFixtures)
3051
end
3152
end
53+
54+
config.after_initialize do
55+
ActiveSupport.on_load(:action_mailbox_record) do
56+
if Rails.application.config.active_record_tenanted.connection_class.present? &&
57+
Rails.application.config.active_record_tenanted.tenanted_rails_records
58+
subtenant_of Rails.application.config.active_record_tenanted.connection_class
59+
end
60+
end
61+
62+
ActiveSupport.on_load(:active_storage_record) do
63+
if Rails.application.config.active_record_tenanted.connection_class.present? &&
64+
Rails.application.config.active_record_tenanted.tenanted_rails_records
65+
subtenant_of Rails.application.config.active_record_tenanted.connection_class
66+
end
67+
end
68+
69+
ActiveSupport.on_load(:action_text_record) do
70+
if Rails.application.config.active_record_tenanted.connection_class.present? &&
71+
Rails.application.config.active_record_tenanted.tenanted_rails_records
72+
subtenant_of Rails.application.config.active_record_tenanted.connection_class
73+
end
74+
end
75+
76+
ActiveSupport.on_load(:active_support_test_case) do
77+
if Rails.application.config.active_record_tenanted.connection_class.present?
78+
klass = Rails.application.config.active_record_tenanted.connection_class.constantize
79+
80+
klass.current_tenant = "#{Rails.env}-tenant" if Rails.env.test?
81+
parallelize_setup do |worker|
82+
klass.current_tenant = "#{Rails.env}-tenant-#{worker}"
83+
end
84+
end
85+
end
86+
end
3287
end
3388
end
3489
end

test/test_helper.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
require_relative "../lib/active_record/tenanted"
1010

11+
# turn off the Rails integrations
12+
ActiveRecord::Tenanted.connection_class = nil
13+
1114
require_relative "dummy/config/environment"
1215
require "minitest/spec"
1316

0 commit comments

Comments
 (0)