ros-apartment 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +93 -2
  4. data/.ruby-version +1 -1
  5. data/Appraisals +15 -0
  6. data/CLAUDE.md +210 -0
  7. data/README.md +1 -1
  8. data/Rakefile +6 -5
  9. data/docs/adapters.md +177 -0
  10. data/docs/architecture.md +274 -0
  11. data/docs/elevators.md +226 -0
  12. data/docs/images/log_example.png +0 -0
  13. data/lib/apartment/CLAUDE.md +300 -0
  14. data/lib/apartment/adapters/CLAUDE.md +314 -0
  15. data/lib/apartment/adapters/abstract_adapter.rb +24 -15
  16. data/lib/apartment/adapters/jdbc_mysql_adapter.rb +1 -1
  17. data/lib/apartment/adapters/jdbc_postgresql_adapter.rb +3 -3
  18. data/lib/apartment/adapters/mysql2_adapter.rb +2 -2
  19. data/lib/apartment/adapters/postgresql_adapter.rb +42 -19
  20. data/lib/apartment/adapters/sqlite3_adapter.rb +7 -7
  21. data/lib/apartment/console.rb +1 -1
  22. data/lib/apartment/custom_console.rb +7 -7
  23. data/lib/apartment/elevators/CLAUDE.md +292 -0
  24. data/lib/apartment/elevators/domain.rb +1 -1
  25. data/lib/apartment/elevators/generic.rb +1 -1
  26. data/lib/apartment/elevators/host_hash.rb +3 -3
  27. data/lib/apartment/elevators/subdomain.rb +9 -5
  28. data/lib/apartment/log_subscriber.rb +1 -1
  29. data/lib/apartment/migrator.rb +2 -2
  30. data/lib/apartment/model.rb +1 -1
  31. data/lib/apartment/railtie.rb +3 -3
  32. data/lib/apartment/tasks/enhancements.rb +1 -1
  33. data/lib/apartment/tasks/task_helper.rb +4 -4
  34. data/lib/apartment/tenant.rb +3 -3
  35. data/lib/apartment/version.rb +1 -1
  36. data/lib/apartment.rb +15 -9
  37. data/lib/generators/apartment/install/install_generator.rb +1 -1
  38. data/lib/generators/apartment/install/templates/apartment.rb +2 -2
  39. data/lib/tasks/apartment.rake +25 -25
  40. data/ros-apartment.gemspec +3 -3
  41. metadata +22 -11
@@ -0,0 +1,300 @@
1
+ # lib/apartment/ - Core Implementation Directory
2
+
3
+ This directory contains the core implementation of Apartment v3's multi-tenancy system.
4
+
5
+ ## Directory Structure
6
+
7
+ ```
8
+ lib/apartment/
9
+ ├── adapters/ # Database-specific tenant isolation strategies
10
+ ├── active_record/ # ActiveRecord patches and extensions
11
+ ├── elevators/ # Rack middleware for automatic tenant switching
12
+ ├── patches/ # Ruby/Rails core patches
13
+ ├── tasks/ # Rake task utilities
14
+ ├── console.rb # Rails console tenant switching utilities
15
+ ├── custom_console.rb # Enhanced console with tenant prompts
16
+ ├── deprecation.rb # Deprecation warnings configuration
17
+ ├── log_subscriber.rb # ActiveSupport instrumentation for logging
18
+ ├── migrator.rb # Tenant-specific migration runner
19
+ ├── model.rb # ActiveRecord model extensions for excluded models
20
+ ├── railtie.rb # Rails initialization and integration
21
+ ├── tenant.rb # Public API facade for tenant operations
22
+ └── version.rb # Gem version constant
23
+ ```
24
+
25
+ ## Core Files
26
+
27
+ ### tenant.rb - Public API Facade
28
+
29
+ **Purpose**: Main entry point for all tenant operations. Delegates to appropriate adapter.
30
+
31
+ **Key methods**:
32
+ - `create(tenant)` - Create new tenant
33
+ - `drop(tenant)` - Delete tenant
34
+ - `switch(tenant)` - Switch to tenant (block-based)
35
+ - `switch!(tenant)` - Immediate switch (no block)
36
+ - `current` - Get current tenant name
37
+ - `reset` - Return to default tenant
38
+ - `each` - Iterate over all tenants
39
+
40
+ **Adapter delegation pattern**: Uses `Forwardable` to delegate all operations to thread-local adapter instance. See delegation setup in `tenant.rb`.
41
+
42
+ **Thread-local storage**: Each thread maintains its own adapter via `Thread.current[:apartment_adapter]`. See `Apartment::Tenant.adapter` method for auto-detection logic.
43
+
44
+ ### railtie.rb - Rails Integration
45
+
46
+ **Purpose**: Integrate Apartment with Rails initialization lifecycle.
47
+
48
+ **Responsibilities**:
49
+ 1. **Configuration loading**: Load `config/initializers/apartment.rb`
50
+ 2. **Adapter initialization**: Call `Apartment::Tenant.init` after Rails boot
51
+ 3. **Console enhancement**: Add tenant switching helpers to Rails console
52
+ 4. **Rake task loading**: Load Apartment rake tasks
53
+ 5. **ActiveRecord instrumentation**: Set up logging subscriber
54
+
55
+ **Key integration points**: See Rails integration hooks in `railtie.rb` (`after_initialize`, `rake_tasks`, `console`).
56
+
57
+ **Excluded models initialization**: The railtie ensures excluded models establish separate connections after Rails boots but before the application serves requests. See excluded model setup in `railtie.rb`.
58
+
59
+ ### console.rb / custom_console.rb - Interactive Debugging
60
+
61
+ **console.rb**: Basic console helpers
62
+ **custom_console.rb**: Enhanced prompt showing current tenant
63
+
64
+ **Features**:
65
+ - Display current tenant in prompt
66
+ - Quick switching helpers
67
+ - Tenant listing commands
68
+
69
+ **Implementation**: See `console.rb` and `custom_console.rb` for prompt customization and helper methods.
70
+
71
+ ### migrator.rb - Tenant Migration Runner
72
+
73
+ **Purpose**: Run migrations across all tenants.
74
+
75
+ **Key functionality**:
76
+ - Detect pending migrations per tenant
77
+ - Run migrations in tenant context
78
+ - Handle migration failures gracefully
79
+ - Support parallel migration execution
80
+
81
+ **Integration**: Used by `rake apartment:migrate` task. See migration coordination logic in `migrator.rb` and task definitions in `tasks/enhancements.rake`.
82
+
83
+ **Parallel execution**: If `config.parallel_migration_threads > 0`, spawns threads to migrate multiple tenants concurrently. See parallel execution logic in `migrator.rb`.
84
+
85
+ ### model.rb - Excluded Model Behavior
86
+
87
+ **Purpose**: Provide base module/behavior for excluded models.
88
+
89
+ **Functionality**:
90
+ - Establish separate connection to default database
91
+ - Bypass tenant switching
92
+ - Maintain global data across tenants
93
+
94
+ **Behavior**: When a model is in `Apartment.excluded_models`, it automatically establishes connection to default database and bypasses tenant switching. See connection handling in `model.rb` and `AbstractAdapter#process_excluded_models`.
95
+
96
+ ### log_subscriber.rb - Instrumentation
97
+
98
+ **Purpose**: Subscribe to ActiveSupport notifications for logging tenant operations.
99
+
100
+ **Events logged**:
101
+ - Tenant creation
102
+ - Tenant switching
103
+ - Tenant deletion
104
+ - Migration execution
105
+
106
+ **Configuration**: Set `config.active_record_log = true` to enable. See event subscriptions in `log_subscriber.rb` and configuration options in `lib/apartment.rb`.
107
+
108
+ ### version.rb - Version Management
109
+
110
+ **Purpose**: Define gem version constant. Used by gemspec and for version checking. See `version.rb`.
111
+
112
+ ### deprecation.rb - Deprecation Warnings
113
+
114
+ **Purpose**: Configure ActiveSupport::Deprecation for Apartment.
115
+
116
+ **Implementation**: Sets up deprecation warnings targeting v4.0. See `deprecation.rb` for DEPRECATOR constant.
117
+
118
+ ## Subdirectories
119
+
120
+ ### adapters/
121
+
122
+ Database-specific implementations of tenant operations. See `lib/apartment/adapters/CLAUDE.md`.
123
+
124
+ **Key files**:
125
+ - `abstract_adapter.rb` - Base adapter with common logic
126
+ - `postgresql_adapter.rb` - PostgreSQL schema-based isolation
127
+ - `mysql2_adapter.rb` - MySQL database-based isolation
128
+ - `sqlite3_adapter.rb` - SQLite file-based isolation
129
+
130
+ ### active_record/
131
+
132
+ ActiveRecord patches and extensions for tenant-aware behavior. See `lib/apartment/active_record/CLAUDE.md`.
133
+
134
+ **Key files**:
135
+ - `connection_handling.rb` - Patches to AR connection management
136
+ - `schema_migration.rb` - Tenant-aware schema_migrations table
137
+ - `postgresql_adapter.rb` - PostgreSQL-specific AR extensions
138
+ - `postgres/schema_dumper.rb` - Custom schema dumping (Rails 7.1+)
139
+
140
+ ### elevators/
141
+
142
+ Rack middleware for automatic tenant detection. See `lib/apartment/elevators/CLAUDE.md`.
143
+
144
+ **Key files**:
145
+ - `generic.rb` - Base elevator with customizable logic
146
+ - `subdomain.rb` - Switch based on subdomain
147
+ - `domain.rb` - Switch based on domain
148
+ - `host.rb` - Switch based on full hostname
149
+ - `host_hash.rb` - Switch based on hostname→tenant mapping
150
+
151
+ ### tasks/
152
+
153
+ Rake task utilities and enhancements.
154
+
155
+ **Key files**:
156
+ - `enhancements.rb` - Rake task definitions (migrate, seed, create, drop)
157
+ - `task_helper.rb` - Shared task utilities
158
+
159
+ ## Data Flow
160
+
161
+ ### Tenant Creation Flow
162
+
163
+ 1. User calls `Apartment::Tenant.create('acme')`
164
+ 2. Delegates to adapter which executes callbacks, creates schema/database, imports schema, optionally runs seeds
165
+ 3. Returns to user code
166
+
167
+ **See**: `Apartment::Tenant.create` and `AbstractAdapter#create` for orchestration.
168
+
169
+ ### Tenant Switching Flow
170
+
171
+ 1. User calls `Apartment::Tenant.switch('acme') { ... }`
172
+ 2. Adapter stores current tenant, switches connection, yields to block, ensures rollback in ensure clause
173
+ 3. Returns to user code with tenant automatically restored
174
+
175
+ **See**: `AbstractAdapter#switch` method for implementation.
176
+
177
+ ### Request Processing Flow (with Elevator)
178
+
179
+ 1. HTTP Request arrives
180
+ 2. Elevator extracts tenant, calls `Apartment::Tenant.switch`
181
+ 3. Application processes in tenant context
182
+ 4. Elevator ensures tenant reset
183
+
184
+ **See**: `elevators/generic.rb` for middleware pattern.
185
+
186
+ ## Thread Safety
187
+
188
+ ### Current Implementation (v3)
189
+
190
+ **Thread-local adapter storage**: Uses `Thread.current[:apartment_adapter]` for isolation.
191
+
192
+ **Implications**:
193
+ - ✅ Each thread has isolated tenant context
194
+ - ✅ Safe for multi-threaded servers (Puma)
195
+ - ✅ Safe for background jobs (Sidekiq)
196
+ - ❌ NOT fiber-safe (fibers share thread storage)
197
+ - ❌ Global mutable state within thread
198
+
199
+ **See**: `Apartment::Tenant.adapter` method for thread-local implementation.
200
+
201
+ ## Configuration Integration
202
+
203
+ ### Loading Process
204
+
205
+ 1. Rails boots
206
+ 2. `config/initializers/apartment.rb` loads
207
+ 3. `Apartment.configure` executes
208
+ 4. Configuration stored in module instance variables
209
+ 5. `Railtie.after_initialize` fires
210
+ 6. `Apartment::Tenant.init` called
211
+ 7. Excluded models processed
212
+ 8. Adapter initialized (lazy, on first use)
213
+
214
+ **See**: Configuration methods in `lib/apartment.rb` and initialization hooks in `railtie.rb`.
215
+
216
+ ### Configuration Access
217
+
218
+ Available configuration methods: `Apartment.tenant_names`, `Apartment.excluded_models`, `Apartment.connection_class`, `Apartment.db_migrate_tenants`. See `lib/apartment.rb` for all configuration options.
219
+
220
+ ## Error Handling
221
+
222
+ ### Exception Hierarchy
223
+
224
+ - `Apartment::ApartmentError` - Base exception for all Apartment errors
225
+ - `Apartment::TenantNotFound` - Raised when switching to nonexistent tenant
226
+ - `Apartment::TenantExists` - Raised when creating duplicate tenant
227
+
228
+ **See**: Adapter `connect_to_new` methods raise `TenantNotFound`. See `AbstractAdapter#switch` for error handling.
229
+
230
+ ### Automatic Cleanup
231
+
232
+ The `switch` method guarantees cleanup via ensure block, falling back to default tenant if rollback fails. See `AbstractAdapter#switch` for implementation.
233
+
234
+ ## Extending Apartment
235
+
236
+ ### Adding Custom Adapter
237
+
238
+ 1. Create file: `lib/apartment/adapters/custom_adapter.rb`
239
+ 2. Subclass `AbstractAdapter`
240
+ 3. Implement required methods
241
+ 4. Add factory method to `tenant.rb`
242
+
243
+ See `docs/adapters.md` for details.
244
+
245
+ ### Adding Custom Elevator
246
+
247
+ 1. Create file: `app/middleware/custom_elevator.rb`
248
+ 2. Subclass `Apartment::Elevators::Generic`
249
+ 3. Override `parse_tenant_name(request)`
250
+ 4. Add to middleware stack in `config/application.rb`
251
+
252
+ See `docs/elevators.md` for details.
253
+
254
+ ### Adding Custom Callbacks
255
+
256
+ Use ActiveSupport::Callbacks to hook into `:create` and `:switch` events. See callback definitions in `AbstractAdapter` and README.md for configuration examples.
257
+
258
+ ## Testing Considerations
259
+
260
+ ### RSpec Integration
261
+
262
+ Always reset tenant context in before/after hooks to prevent test isolation issues. See `spec/support/` for helper modules and `spec/spec_helper.rb` for configuration patterns.
263
+
264
+ ### Creating Test Tenants
265
+
266
+ Create helpers for tenant lifecycle management to avoid duplication. See `spec/support/apartment_helper.rb` for patterns.
267
+
268
+ ## Debugging Tips
269
+
270
+ ### Enable Verbose Logging
271
+
272
+ Set `config.active_record_log = true` in initializer. See logging configuration in `lib/apartment.rb`.
273
+
274
+ ### Check Current Tenant
275
+
276
+ Use `Apartment::Tenant.current` to inspect current tenant context.
277
+
278
+ ### Inspect Adapter
279
+
280
+ Access `Apartment::Tenant.adapter` to inspect adapter class and configuration.
281
+
282
+ ### Verify Excluded Models
283
+
284
+ Iterate `Apartment.excluded_models` and check each model's connection configuration.
285
+
286
+ ## Common Pitfalls
287
+
288
+ 1. **Not using block-based switching**: Always use `switch` with block, not `switch!`
289
+ 2. **Elevator positioning**: Must be before session/auth middleware
290
+ 3. **Excluded model relationships**: Use `has_many :through`, not `has_and_belongs_to_many`
291
+ 4. **Thread safety assumptions**: Remember adapters are thread-local, not global
292
+ 5. **Forgetting to reset**: In tests, always reset tenant in teardown
293
+
294
+ ## References
295
+
296
+ - Main README: `/README.md`
297
+ - Architecture docs: `/docs/architecture.md`
298
+ - Adapter docs: `/docs/adapters.md`
299
+ - Elevator docs: `/docs/elevators.md`
300
+ - ActiveRecord connection handling: Rails guides
@@ -0,0 +1,314 @@
1
+ # lib/apartment/adapters/ - Database Adapter Implementations
2
+
3
+ This directory contains database-specific implementations of tenant isolation strategies.
4
+
5
+ ## Purpose
6
+
7
+ Adapters translate abstract tenant operations (create, switch, drop) into database-specific SQL commands and connection management.
8
+
9
+ ## File Structure
10
+
11
+ ```
12
+ adapters/
13
+ ├── abstract_adapter.rb # Base class with shared logic
14
+ ├── postgresql_adapter.rb # PostgreSQL schema-based isolation
15
+ ├── postgis_adapter.rb # PostgreSQL with PostGIS extensions
16
+ ├── mysql2_adapter.rb # MySQL database-based isolation (mysql2 gem)
17
+ ├── trilogy_adapter.rb # MySQL database-based isolation (trilogy gem)
18
+ ├── sqlite3_adapter.rb # SQLite file-based isolation
19
+ ├── abstract_jdbc_adapter.rb # Base for JDBC adapters (JRuby)
20
+ ├── jdbc_postgresql_adapter.rb # JDBC PostgreSQL adapter
21
+ └── jdbc_mysql_adapter.rb # JDBC MySQL adapter
22
+ ```
23
+
24
+ ## Adapter Hierarchy
25
+
26
+ ```
27
+ AbstractAdapter
28
+ ├── PostgresqlAdapter
29
+ │ ├── PostgisAdapter (PostgreSQL + spatial extensions)
30
+ │ └── JdbcPostgresqlAdapter (JDBC for JRuby)
31
+ ├── Mysql2Adapter
32
+ │ ├── TrilogyAdapter (alternative MySQL driver)
33
+ │ └── JdbcMysqlAdapter (JDBC for JRuby)
34
+ └── Sqlite3Adapter
35
+ ```
36
+
37
+ ## AbstractAdapter - Base Implementation
38
+
39
+ **Location**: `abstract_adapter.rb`
40
+
41
+ ### Responsibilities
42
+
43
+ 1. **Common tenant lifecycle logic**:
44
+ - Callback execution (`:create`, `:switch`)
45
+ - Schema import coordination
46
+ - Seed data execution
47
+ - Exception handling
48
+
49
+ 2. **Excluded model management**:
50
+ - Establish separate connections for excluded models
51
+ - Ensure they bypass tenant switching
52
+
53
+ 3. **Helper methods**:
54
+ - `environmentify(tenant)` - Add Rails env to tenant name
55
+ - `seed_data` - Load seeds.rb in tenant context
56
+ - `each(tenants)` - Iterate over tenants
57
+
58
+ ### Abstract Methods (Subclasses Must Implement)
59
+
60
+ - `create_tenant(tenant)` - Create the tenant (schema/database/file)
61
+ - `connect_to_new(tenant)` - Switch to tenant (change connection or search_path)
62
+ - `drop_command(conn, tenant)` - Drop the tenant
63
+ - `current` - Get current tenant name
64
+
65
+ **See**: Abstract method definitions in `abstract_adapter.rb`.
66
+
67
+ ### Common Logic Provided
68
+
69
+ **Tenant creation**: Runs callbacks, creates tenant via subclass, switches context, imports schema, optionally seeds data. See `AbstractAdapter#create` method.
70
+
71
+ **Tenant switching**: Stores previous tenant, switches, yields to block, ensures rollback in ensure clause with fallback to default. See `AbstractAdapter#switch` method.
72
+
73
+ **Schema import**: Loads `db/schema.rb` or custom schema file. See schema import logic in `abstract_adapter.rb`.
74
+
75
+ ### Helper Methods
76
+
77
+ **Environmentify**: Adds Rails environment prefix/suffix to tenant name based on configuration. See `AbstractAdapter#environmentify` method.
78
+
79
+ **Excluded model processing**: Establishes separate connections for excluded models. See `AbstractAdapter#process_excluded_models` method.
80
+
81
+ ## PostgreSQL Adapter
82
+
83
+ **Location**: `postgresql_adapter.rb`
84
+
85
+ ### Strategy
86
+
87
+ Uses **PostgreSQL schemas** (namespaces) for tenant isolation.
88
+
89
+ ### Key Implementation Details
90
+
91
+ **Create tenant**: Executes `CREATE SCHEMA` SQL command. See `PostgresqlAdapter#create_tenant` method.
92
+
93
+ **Switch tenant**: Changes `search_path` to target schema. See `PostgresqlAdapter#connect_to_new` method.
94
+
95
+ **Drop tenant**: Executes `DROP SCHEMA CASCADE`. See `PostgresqlAdapter#drop_command` method.
96
+
97
+ **Get current tenant**: Returns instance variable tracking current schema. See `PostgresqlAdapter#current` method.
98
+
99
+ ### Search Path Mechanics
100
+
101
+ PostgreSQL searches schemas in order defined by `search_path`. Queries resolve to first matching table. Search path includes tenant schema, persistent schemas, then public. See search path construction in `PostgresqlAdapter#connect_to_new`.
102
+
103
+ ### Persistent Schemas
104
+
105
+ Configured via `config.persistent_schemas` to specify schemas that remain in search path across all tenants.
106
+
107
+ **Use cases**:
108
+ - Shared PostgreSQL extensions (uuid-ossp, hstore, postgis)
109
+ - Utility functions/views shared across tenants
110
+ - Reference data tables
111
+
112
+ **See**: README.md for configuration examples.
113
+
114
+ ### Excluded Names (pg_excluded_names)
115
+
116
+ Configured via `config.pg_excluded_names` to exclude tables/schemas from tenant cloning.
117
+
118
+ **Use cases**:
119
+ - Temporary tables
120
+ - Backup tables
121
+ - Staging/import tables
122
+
123
+ **See**: README.md for configuration patterns.
124
+
125
+ ### Performance Characteristics
126
+
127
+ - **Switching**: <1ms (SQL command)
128
+ - **Memory**: ~50MB total (shared connection pool)
129
+ - **Scalability**: 100+ tenants easily
130
+ - **Isolation**: Schema-level (good, not absolute)
131
+
132
+ ## PostGIS Adapter
133
+
134
+ **Location**: `postgis_adapter.rb`
135
+
136
+ ### Strategy
137
+
138
+ Extends `PostgresqlAdapter` with PostGIS spatial extension support.
139
+
140
+ ### Key Differences
141
+
142
+ **Tenant creation**: Extends base PostgresqlAdapter to automatically enable PostGIS extensions in new schemas. See `PostgisAdapter#create_tenant` method.
143
+
144
+ **Schema dumping**: Custom logic to handle spatial types and indexes correctly. See `active_record/postgres/schema_dumper.rb`.
145
+
146
+ ### Configuration
147
+
148
+ Typically includes PostGIS-related schemas in `persistent_schemas`. See README.md for configuration.
149
+
150
+ ## MySQL Adapters
151
+
152
+ **Locations**: `mysql2_adapter.rb`, `trilogy_adapter.rb`
153
+
154
+ ### Strategy
155
+
156
+ Uses **separate databases** for each tenant.
157
+
158
+ ### Key Implementation Details
159
+
160
+ **Create tenant**: Executes `CREATE DATABASE` SQL command. See `Mysql2Adapter#create_tenant` method.
161
+
162
+ **Switch tenant**: Establishes new connection with different database name. See `Mysql2Adapter#connect_to_new` method.
163
+
164
+ **Drop tenant**: Executes `DROP DATABASE`. See `Mysql2Adapter#drop_command` method.
165
+
166
+ **Get current database**: Queries current database name from connection. See `Mysql2Adapter#current` method.
167
+
168
+ ### Connection Management
169
+
170
+ Each tenant switch establishes new connection to different database. This creates connection pool overhead compared to PostgreSQL schemas. See `Mysql2Adapter#connect_to_new` for connection establishment.
171
+
172
+ ### Multi-Server Support
173
+
174
+ MySQL adapters support hash-based configuration mapping tenant names to full connection configs, enabling different tenants on different servers. See README.md for configuration examples.
175
+
176
+ ### Performance Characteristics
177
+
178
+ - **Switching**: 10-50ms (connection establishment)
179
+ - **Memory**: ~20MB per active tenant (connection pool)
180
+ - **Scalability**: 10-50 concurrent tenants
181
+ - **Isolation**: Database-level (excellent)
182
+
183
+ ### Trilogy Adapter
184
+
185
+ **Location**: `trilogy_adapter.rb`
186
+
187
+ Identical to `Mysql2Adapter` but uses the `trilogy` gem (modern MySQL client).
188
+
189
+ **Usage**: Auto-selected if `adapter: trilogy` in `database.yml`.
190
+
191
+ ## SQLite Adapter
192
+
193
+ **Location**: `sqlite3_adapter.rb`
194
+
195
+ ### Strategy
196
+
197
+ Uses **separate database files** for each tenant.
198
+
199
+ ### Key Implementation Details
200
+
201
+ **Create tenant**: Creates new SQLite file and establishes connection. See `Sqlite3Adapter#create_tenant` method.
202
+
203
+ **Switch tenant**: Establishes connection to different database file. See `Sqlite3Adapter#connect_to_new` method.
204
+
205
+ **Drop tenant**: Deletes database file. See `Sqlite3Adapter#drop_command` method.
206
+
207
+ **Database file path**: Constructs file path in db/ directory. See file path construction in `Sqlite3Adapter`.
208
+
209
+ ### Use Cases
210
+
211
+ - ✅ **Testing**: Each test tenant is isolated file
212
+ - ✅ **Development**: Easy to inspect individual tenant data
213
+ - ✅ **Single-user apps**: Desktop or embedded applications
214
+ - ❌ **Production**: Not suitable for concurrent multi-user access
215
+
216
+ ### Performance Characteristics
217
+
218
+ - **Switching**: 5-20ms (file I/O + connection)
219
+ - **Memory**: ~5MB per database file
220
+ - **Scalability**: Not recommended for production multi-tenant
221
+ - **Isolation**: Complete (separate files)
222
+
223
+ ## JDBC Adapters (JRuby)
224
+
225
+ **Locations**: `abstract_jdbc_adapter.rb`, `jdbc_postgresql_adapter.rb`, `jdbc_mysql_adapter.rb`
226
+
227
+ ### Purpose
228
+
229
+ Support JRuby deployments using JDBC drivers.
230
+
231
+ ### Implementation
232
+
233
+ Inherit from standard adapters but use JDBC-specific connection handling. See `jdbc_postgresql_adapter.rb` and `jdbc_mysql_adapter.rb`.
234
+
235
+ ### Auto-Detection
236
+
237
+ JRuby detection happens in `tenant.rb` - automatically selects JDBC adapters when running on JRuby. See adapter factory logic in `Apartment::Tenant.adapter_method`.
238
+
239
+ ## Adapter Selection Matrix
240
+
241
+ | Adapter | Database Type | Strategy | Speed | Scalability | Isolation | Best For |
242
+ |------------------------|---------------|--------------|--------------|-------------|-----------|-------------------------|
243
+ | PostgresqlAdapter | PostgreSQL | Schemas | Very Fast | Excellent | Good | 100+ tenants |
244
+ | PostgisAdapter | PostGIS | Schemas | Very Fast | Excellent | Good | Spatial data apps |
245
+ | Mysql2Adapter | MySQL | Databases | Moderate | Good | Excellent | Complete isolation |
246
+ | TrilogyAdapter | MySQL | Databases | Moderate | Good | Excellent | Modern MySQL client |
247
+ | Sqlite3Adapter | SQLite | Files | Moderate | Poor | Excellent | Testing, development |
248
+ | JdbcPostgresqlAdapter | PostgreSQL | Schemas | Very Fast | Excellent | Good | JRuby deployments |
249
+ | JdbcMysqlAdapter | MySQL | Databases | Moderate | Good | Excellent | JRuby deployments |
250
+
251
+ ## Creating Custom Adapters
252
+
253
+ To support new databases: subclass `AbstractAdapter`, implement required methods (`create_tenant`, `connect_to_new`, `drop_command`, `current`), register factory method in `tenant.rb`, and configure in `database.yml`.
254
+
255
+ **See**: Existing adapters for patterns (`postgresql_adapter.rb` is most complex, `sqlite3_adapter.rb` is simplest), and `docs/adapters.md` for design rationale.
256
+
257
+ ## Testing Adapters
258
+
259
+ ### Adapter-Specific Tests
260
+
261
+ Each adapter has comprehensive specs covering tenant creation, switching, deletion, error handling, and callbacks. See `spec/adapters/` for test patterns.
262
+
263
+ ## Debugging Adapters
264
+
265
+ ### Check Current Adapter
266
+
267
+ Use `Apartment::Tenant.adapter.class.name` to inspect adapter type.
268
+
269
+ ### Inspect Configuration
270
+
271
+ Access `adapter.instance_variable_get(:@config)` for configuration and `adapter.default_tenant` for default.
272
+
273
+ ### Database-Specific Debugging
274
+
275
+ **PostgreSQL**: Execute `SHOW search_path` to verify current schema search path.
276
+
277
+ **MySQL**: Execute `SELECT DATABASE()` to verify current database name.
278
+
279
+ ## Common Issues
280
+
281
+ ### Issue: Schema/Database Not Created
282
+
283
+ **Cause**: Permissions, invalid names, or database errors
284
+
285
+ **Debug**: Wrap `Apartment::Tenant.create` in rescue block and inspect exception class and message.
286
+
287
+ ### Issue: Switching Fails
288
+
289
+ **Cause**: Tenant doesn't exist or connection issues
290
+
291
+ **Debug**: Verify tenant in `Apartment.tenant_names` and check `adapter.current` state.
292
+
293
+ ### Issue: Wrong Data After Switch
294
+
295
+ **Cause**: Improper cleanup or middleware ordering
296
+
297
+ **Solution**: Always use block-based switching, verify middleware order.
298
+
299
+ ## Performance Optimization
300
+
301
+ ### PostgreSQL: Connection Pooling
302
+
303
+ PostgreSQL adapters use shared connection pool across all tenants. Configure pool size in `database.yml`. See Rails connection pooling guides.
304
+
305
+ ### MySQL: Connection Pool Caching
306
+
307
+ Consider implementing LRU cache for connection pools to limit memory usage with many tenants. Not implemented in v3 but possible via custom adapter.
308
+
309
+ ## References
310
+
311
+ - PostgreSQL schemas: https://www.postgresql.org/docs/current/ddl-schemas.html
312
+ - MySQL databases: https://dev.mysql.com/doc/refman/8.0/en/creating-database.html
313
+ - ActiveRecord adapters: Rails source code
314
+ - AbstractAdapter source: `abstract_adapter.rb`