Module: Apartment

Defined in:
lib/apartment.rb,
lib/apartment/cli.rb,
lib/apartment/config.rb,
lib/apartment/errors.rb,
lib/apartment/tenant.rb,
lib/apartment/current.rb,
lib/apartment/railtie.rb,
lib/apartment/version.rb,
lib/apartment/cli/pool.rb,
lib/apartment/migrator.rb,
lib/apartment/cli/seeds.rb,
lib/apartment/cli/tenants.rb,
lib/apartment/pool_reaper.rb,
lib/apartment/pool_manager.rb,
lib/apartment/schema_cache.rb,
lib/apartment/cli/migrations.rb,
lib/apartment/concerns/model.rb,
lib/apartment/elevators/host.rb,
lib/apartment/instrumentation.rb,
lib/apartment/elevators/domain.rb,
lib/apartment/elevators/header.rb,
lib/apartment/elevators/generic.rb,
lib/apartment/elevators/host_hash.rb,
lib/apartment/elevators/subdomain.rb,
lib/apartment/schema_dumper_patch.rb,
lib/apartment/configs/mysql_config.rb,
lib/apartment/tenant_name_validator.rb,
lib/apartment/adapters/mysql2_adapter.rb,
lib/apartment/adapters/sqlite3_adapter.rb,
lib/apartment/adapters/trilogy_adapter.rb,
lib/apartment/adapters/abstract_adapter.rb,
lib/apartment/configs/postgresql_config.rb,
lib/apartment/elevators/first_subdomain.rb,
lib/apartment/patches/connection_handling.rb,
lib/apartment/adapters/postgresql_schema_adapter.rb,
lib/apartment/adapters/postgresql_database_adapter.rb,
lib/generators/apartment/install/install_generator.rb

Defined Under Namespace

Modules: Adapters, Configs, Elevators, Instrumentation, Model, Patches, SchemaCache, SchemaDumperPatch, Tenant, TenantNameValidator Classes: AdapterNotFound, ApartmentError, CLI, Config, ConfigurationError, Current, InstallGenerator, Migrator, PendingMigrationError, PoolExhausted, PoolManager, PoolReaper, Railtie, SchemaLoadError, TenantExists, TenantNotFound

Constant Summary collapse

VERSION =
'4.0.0.alpha1'

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.adapterObject

Lazy-loading adapter. Built on first access via build_adapter. Can be set manually (e.g., in tests) via Apartment.adapter=.



44
45
46
# File 'lib/apartment.rb', line 44

def adapter
  @adapter ||= build_adapter
end

.configObject (readonly)

Returns the value of attribute config.



39
40
41
# File 'lib/apartment.rb', line 39

def config
  @config
end

.pool_managerObject (readonly)

Returns the value of attribute pool_manager.



39
40
41
# File 'lib/apartment.rb', line 39

def pool_manager
  @pool_manager
end

.pool_reaperObject (readonly)

Returns the value of attribute pool_reaper.



39
40
41
# File 'lib/apartment.rb', line 39

def pool_reaper
  @pool_reaper
end

Class Method Details

.activate!Object

Activate the ConnectionHandling patch on ActiveRecord::Base. Idempotent — prepend on an already-prepended module is a no-op.



122
123
124
125
126
# File 'lib/apartment.rb', line 122

def activate!
  require_relative('apartment/patches/connection_handling')
  ActiveRecord::Base.singleton_class.prepend(Patches::ConnectionHandling)
  @activated = true
end

.activate_sql_query_tags!Object

Register a :tenant tag with ActiveRecord::QueryLogs so SQL queries include a /* tenant=‘name’ */ comment. No-op when sql_query_tags is false or ActiveRecord::QueryLogs is not available.



131
132
133
134
135
136
137
138
139
140
# File 'lib/apartment.rb', line 131

def activate_sql_query_tags!
  return unless @config&.sql_query_tags
  return unless defined?(ActiveRecord::QueryLogs)
  return if ActiveRecord::QueryLogs.tags.include?(:tenant)

  ActiveRecord::QueryLogs.taggings = ActiveRecord::QueryLogs.taggings.merge(
    tenant: -> { Apartment::Current.tenant }
  )
  ActiveRecord::QueryLogs.tags = ActiveRecord::QueryLogs.tags + [:tenant]
end

.activated?Boolean

Returns:

  • (Boolean)


64
65
66
# File 'lib/apartment.rb', line 64

def activated?
  @activated == true
end

.clear_configObject

Reset all configuration and stop background tasks.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/apartment.rb', line 105

def clear_config
  teardown_old_state
  # Reset per-model processing flags so re-configuration re-establishes connections.
  @pinned_models&.each do |klass|
    next unless klass.instance_variable_defined?(:@apartment_connection_established)

    klass.remove_instance_variable(:@apartment_connection_established)
  end
  @config = nil
  @pool_manager = nil
  @pool_reaper = nil
  @pinned_models = nil
  @activated = false
end

.configure {|new_config| ... } ⇒ Object

Configure Apartment v4. Yields a Config instance, validates it, and prepares the module for use.

Apartment.configure do |config|
  config.tenant_strategy = :schema
  config.tenants_provider = -> { Tenant.pluck(:name) }
end

Yields:

  • (new_config)

Raises:



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/apartment.rb', line 80

def configure
  raise(ConfigurationError, 'Apartment.configure requires a block') unless block_given?

  new_config = Config.new
  yield(new_config)
  new_config.validate!
  new_config.freeze!

  # Validation passed — tear down old state and swap in new.
  teardown_old_state
  @config = new_config
  @pool_manager = PoolManager.new
  @pool_reaper = PoolReaper.new(
    pool_manager: @pool_manager,
    interval: new_config.pool_idle_timeout,
    idle_timeout: new_config.pool_idle_timeout,
    max_total: new_config.max_total_connections,
    default_tenant: new_config.default_tenant,
    shard_key_prefix: new_config.shard_key_prefix
  )
  @pool_reaper.start
  @config
end

.deregister_shard(pool_key) ⇒ Object

Deregister a single tenant’s shard from AR’s ConnectionHandler. Safe to call when AR is not loaded or config is not set (no-op). Used by PoolReaper eviction, AbstractAdapter#drop, and teardown.



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/apartment.rb', line 145

def deregister_shard(pool_key)
  return unless @config && defined?(ActiveRecord::Base)

  _, separator, role_str = pool_key.to_s.rpartition(':')
  role = separator.empty? || role_str.empty? ? ActiveRecord.writing_role : role_str.to_sym

  shard_key = :"#{@config.shard_key_prefix}_#{pool_key}"
  ActiveRecord::Base.connection_handler.remove_connection_pool(
    'ActiveRecord::Base',
    role: role,
    shard: shard_key
  )
rescue StandardError => e
  warn "[Apartment] Failed to deregister AR pool for #{pool_key}: #{e.class}: #{e.message}"
end

.pinned_model?(klass) ⇒ Boolean

Check if a class (or any of its ancestors) is a pinned model. Used by ConnectionHandling to skip tenant pool routing.

Returns:

  • (Boolean)


60
61
62
# File 'lib/apartment.rb', line 60

def pinned_model?(klass)
  klass.ancestors.any? { |a| a.is_a?(Class) && pinned_models.include?(a) }
end

.pinned_modelsObject

Registry of models that declared pin_tenant. Uses Concurrent::Set for thread safety (Zeitwerk autoload in threaded servers).



50
51
52
# File 'lib/apartment.rb', line 50

def pinned_models
  @pinned_models ||= Concurrent::Set.new
end

.process_pinned_model(klass) ⇒ Object



68
69
70
# File 'lib/apartment.rb', line 68

def process_pinned_model(klass)
  adapter&.process_pinned_model(klass)
end

.register_pinned_model(klass) ⇒ Object



54
55
56
# File 'lib/apartment.rb', line 54

def register_pinned_model(klass)
  pinned_models.add(klass)
end