Class: Apartment::Adapters::AbstractAdapter
- Inherits:
-
Object
- Object
- Apartment::Adapters::AbstractAdapter
- Includes:
- ActiveSupport::Callbacks
- Defined in:
- lib/apartment/adapters/abstract_adapter.rb
Overview
rubocop:disable Metrics/ClassLength
Direct Known Subclasses
Mysql2Adapter, PostgresqlDatabaseAdapter, PostgresqlSchemaAdapter, Sqlite3Adapter
Instance Attribute Summary collapse
-
#connection_config ⇒ Object
readonly
The raw database connection configuration hash (from ActiveRecord).
Instance Method Summary collapse
-
#create(tenant) ⇒ Object
Create a new tenant (schema or database).
-
#default_tenant ⇒ Object
Default tenant from config.
-
#drop(tenant) ⇒ Object
Drop a tenant.
-
#environmentify(tenant) ⇒ Object
Environmentify a tenant name based on config.
-
#initialize(connection_config) ⇒ AbstractAdapter
constructor
A new instance of AbstractAdapter.
-
#migrate(tenant, version = nil) ⇒ Object
Run migrations for a tenant.
-
#process_excluded_models ⇒ Object
Deprecated: use process_pinned_models instead.
-
#process_pinned_model(klass) ⇒ Object
Process a single pinned model.
-
#process_pinned_models ⇒ Object
Process all pinned models — establish separate connections pinned to default tenant.
-
#resolve_connection_config(tenant, base_config: nil) ⇒ Object
Resolve a tenant-specific connection config hash.
-
#seed(tenant) ⇒ Object
Run seeds for a tenant.
-
#validated_connection_config(tenant, base_config_override: nil) ⇒ Object
Template method: validates tenant name then delegates to resolve_connection_config.
Constructor Details
#initialize(connection_config) ⇒ AbstractAdapter
Returns a new instance of AbstractAdapter.
18 19 20 |
# File 'lib/apartment/adapters/abstract_adapter.rb', line 18 def initialize(connection_config) @connection_config = connection_config end |
Instance Attribute Details
#connection_config ⇒ Object (readonly)
The raw database connection configuration hash (from ActiveRecord). Not to be confused with Apartment.config (the Apartment::Config object).
16 17 18 |
# File 'lib/apartment/adapters/abstract_adapter.rb', line 16 def connection_config @connection_config end |
Instance Method Details
#create(tenant) ⇒ Object
Create a new tenant (schema or database).
43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/apartment/adapters/abstract_adapter.rb', line 43 def create(tenant) TenantNameValidator.validate!( environmentify(tenant), strategy: Apartment.config.tenant_strategy, adapter_name: base_config['adapter'] ) run_callbacks(:create) do create_tenant(tenant) grant_tenant_privileges(tenant) import_schema(tenant) if Apartment.config.schema_load_strategy seed(tenant) if Apartment.config.seed_after_create Instrumentation.instrument(:create, tenant: tenant) end end |
#default_tenant ⇒ Object
Default tenant from config.
156 157 158 |
# File 'lib/apartment/adapters/abstract_adapter.rb', line 156 def default_tenant Apartment.config.default_tenant end |
#drop(tenant) ⇒ Object
Drop a tenant.
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/apartment/adapters/abstract_adapter.rb', line 59 def drop(tenant) # rubocop:disable Metrics/CyclomaticComplexity drop_tenant(tenant) removed_pools = Apartment.pool_manager&.remove_tenant(tenant) || [] removed_pools.each do |pool_key, pool| begin pool&.disconnect! if pool.respond_to?(:disconnect!) rescue StandardError => e warn "[Apartment] Pool disconnect failed for '#{pool_key}': #{e.class}: #{e.}" end begin deregister_shard_from_ar_handler(pool_key) rescue StandardError => e warn "[Apartment] Shard deregistration failed for '#{pool_key}': #{e.class}: #{e.}" end end Instrumentation.instrument(:drop, tenant: tenant) end |
#environmentify(tenant) ⇒ Object
Environmentify a tenant name based on config. :prepend/:append require Rails to be defined (for Rails.env).
141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/apartment/adapters/abstract_adapter.rb', line 141 def environmentify(tenant) case Apartment.config.environmentify_strategy when :prepend "#{rails_env}_#{tenant}" when :append "#{tenant}_#{rails_env}" when nil tenant.to_s else # Callable Apartment.config.environmentify_strategy.call(tenant) end end |
#migrate(tenant, version = nil) ⇒ Object
Run migrations for a tenant.
78 79 80 81 82 |
# File 'lib/apartment/adapters/abstract_adapter.rb', line 78 def migrate(tenant, version = nil) Apartment::Tenant.switch(tenant) do ActiveRecord::Base.connection_pool.migration_context.migrate(version) end end |
#process_excluded_models ⇒ Object
Deprecated: use process_pinned_models instead.
133 134 135 136 137 |
# File 'lib/apartment/adapters/abstract_adapter.rb', line 133 def process_excluded_models warn '[Apartment] DEPRECATION: process_excluded_models is deprecated. ' \ 'Use Apartment::Model with pin_tenant instead.' process_pinned_models end |
#process_pinned_model(klass) ⇒ Object
Process a single pinned model. Called by process_pinned_models (batch) and by Apartment::Model.pin_tenant (when activated? is true).
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/apartment/adapters/abstract_adapter.rb', line 110 def process_pinned_model(klass) # Idempotent: skip if already processed. Uses a class-level flag rather # than connection_specification_name comparison — the spec name differs # from ActiveRecord::Base for ApplicationRecord subclasses even before # establish_connection, so it's not a reliable "already processed" signal. return if klass.instance_variable_get(:@apartment_connection_established) # Use base_config (the adapter's raw connection config) rather than # resolve_connection_config(default_tenant). For database-per-tenant # strategies (MySQL, SQLite), resolve_connection_config would set the # database key to the default tenant NAME (e.g. 'default'), not the # actual default database (e.g. 'apartment_v4_test'). base_config # points to the real default database. klass.establish_connection(base_config) klass.instance_variable_set(:@apartment_connection_established, true) return unless Apartment.config.tenant_strategy == :schema table = klass.table_name.split('.').last klass.table_name = "#{default_tenant}.#{table}" end |
#process_pinned_models ⇒ Object
Process all pinned models — establish separate connections pinned to default tenant.
100 101 102 103 104 105 106 |
# File 'lib/apartment/adapters/abstract_adapter.rb', line 100 def process_pinned_models return if Apartment.pinned_models.empty? Apartment.pinned_models.each do |klass| process_pinned_model(klass) end end |
#resolve_connection_config(tenant, base_config: nil) ⇒ Object
Resolve a tenant-specific connection config hash. Subclasses override to set strategy-specific keys.
38 39 40 |
# File 'lib/apartment/adapters/abstract_adapter.rb', line 38 def resolve_connection_config(tenant, base_config: nil) raise(NotImplementedError) end |
#seed(tenant) ⇒ Object
Run seeds for a tenant.
85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/apartment/adapters/abstract_adapter.rb', line 85 def seed(tenant) Apartment::Tenant.switch(tenant) do seed_file = Apartment.config.seed_data_file return unless seed_file unless File.exist?(seed_file) raise(Apartment::ConfigurationError, "Seed file '#{seed_file}' does not exist") end load(seed_file) end end |
#validated_connection_config(tenant, base_config_override: nil) ⇒ Object
Template method: validates tenant name then delegates to resolve_connection_config. Called by ConnectionHandling — subclasses should NOT override this. base_config_override: when supplied (e.g. a role-specific config from ConnectionHandling), the adapter builds the tenant config on top of it instead of its own base_config.
26 27 28 29 30 31 32 33 34 |
# File 'lib/apartment/adapters/abstract_adapter.rb', line 26 def validated_connection_config(tenant, base_config_override: nil) effective_base = base_config_override || base_config TenantNameValidator.validate!( tenant, strategy: Apartment.config.tenant_strategy, adapter_name: effective_base['adapter'] ) resolve_connection_config(tenant, base_config: effective_base) end |