class Apartment::Adapters::AbstractAdapter

Abstract adapter from which all the Apartment DB related adapters will inherit the base logic

Attributes

default_tenant[W]

Public Class Methods

new(config) click to toggle source

@constructor @param {Hash} config Database config

# File lib/apartment/adapters/abstract_adapter.rb, line 15
def initialize(config)
  @config = config
end

Public Instance Methods

create(tenant) { || ... } click to toggle source

Create a new tenant, import schema, seed if appropriate

@param {String} tenant Tenant name

# File lib/apartment/adapters/abstract_adapter.rb, line 23
def create(tenant)
  run_callbacks :create do
    create_tenant(tenant)

    switch(tenant) do
      import_database_schema

      # Seed data if appropriate
      seed_data if Apartment.seed_after_create

      yield if block_given?
    end
  end
end
current() click to toggle source

Note alias_method here doesn't work with inheritence apparently ??

# File lib/apartment/adapters/abstract_adapter.rb, line 46
def current
  Apartment.connection.current_database
end
default_tenant() click to toggle source

Return the original public tenant

@return {String} default tenant name

# File lib/apartment/adapters/abstract_adapter.rb, line 54
def default_tenant
  @default_tenant || Apartment.default_tenant
end
drop(tenant) click to toggle source

Drop the tenant

@param {String} tenant name

# File lib/apartment/adapters/abstract_adapter.rb, line 62
def drop(tenant)
  with_neutral_connection(tenant) do |conn|
    drop_command(conn, tenant)
  end
rescue *rescuable_exceptions => e
  raise_drop_tenant_error!(tenant, e)
end
each(tenants = Apartment.tenant_names) { |tenant| ... } click to toggle source

Iterate over all tenants, switch to tenant and yield tenant name

# File lib/apartment/adapters/abstract_adapter.rb, line 100
def each(tenants = Apartment.tenant_names)
  tenants.each do |tenant|
    switch(tenant) { yield tenant }
  end
end
environmentify(tenant) click to toggle source

Prepend the environment if configured and the environment isn't already there

@param {String} tenant Database name @return {String} tenant name with Rails environment optionally prepended

# File lib/apartment/adapters/abstract_adapter.rb, line 135
def environmentify(tenant)
  return tenant if tenant.nil? || tenant.include?(Rails.env)

  if Apartment.prepend_environment
    "#{Rails.env}_#{tenant}"
  elsif Apartment.append_environment
    "#{tenant}_#{Rails.env}"
  else
    tenant
  end
end
init() click to toggle source

Initialize Apartment config options such as excluded_models

# File lib/apartment/adapters/abstract_adapter.rb, line 40
def init
  process_excluded_models
end
process_excluded_models() click to toggle source

Establish a new connection for each specific excluded model

# File lib/apartment/adapters/abstract_adapter.rb, line 108
def process_excluded_models
  # All other models will shared a connection (at Apartment.connection_class)
  # and we can modify at will
  Apartment.excluded_models.each do |excluded_model|
    process_excluded_model(excluded_model)
  end
end
reset() click to toggle source

Reset the tenant connection to the default

# File lib/apartment/adapters/abstract_adapter.rb, line 118
def reset
  Apartment.establish_connection @config
end
seed()
Alias for: seed_data
seed_data() click to toggle source

Load the rails seed file into the db

# File lib/apartment/adapters/abstract_adapter.rb, line 124
def seed_data
  # Don't log the output of seeding the db
  silence_warnings { load_or_raise(Apartment.seed_data_file) } if Apartment.seed_data_file
end
Also aliased as: seed
switch(tenant = nil) { || ... } click to toggle source

Connect to tenant, do your biz, switch back to previous tenant

@param {String?} tenant to connect to

# File lib/apartment/adapters/abstract_adapter.rb, line 86
def switch(tenant = nil)
  previous_tenant = current
  switch!(tenant)
  yield
ensure
  begin
    switch!(previous_tenant)
  rescue StandardError => _e
    reset
  end
end
switch!(tenant = nil) click to toggle source

Switch to a new tenant

@param {String} tenant name

# File lib/apartment/adapters/abstract_adapter.rb, line 74
def switch!(tenant = nil)
  run_callbacks :switch do
    connect_to_new(tenant).tap do
      Apartment.connection.clear_query_cache
    end
  end
end

Protected Instance Methods

connect_to_new(tenant) click to toggle source

Connect to new tenant

@param {String} tenant Database name

# File lib/apartment/adapters/abstract_adapter.rb, line 178
def connect_to_new(tenant)
  return reset if tenant.nil?

  query_cache_enabled = ActiveRecord::Base.connection.query_cache_enabled

  Apartment.establish_connection multi_tenantify(tenant)
  Apartment.connection.active? # call active? to manually check if this connection is valid

  Apartment.connection.enable_query_cache! if query_cache_enabled
rescue *rescuable_exceptions => e
  Apartment::Tenant.reset if reset_on_connection_exception?
  raise_connect_error!(tenant, e)
end
create_tenant(tenant) click to toggle source

Create the tenant

@param {String} tenant Database name

# File lib/apartment/adapters/abstract_adapter.rb, line 162
def create_tenant(tenant)
  with_neutral_connection(tenant) do |conn|
    create_tenant_command(conn, tenant)
  end
rescue *rescuable_exceptions => e
  raise_create_tenant_error!(tenant, e)
end
create_tenant_command(conn, tenant) click to toggle source
# File lib/apartment/adapters/abstract_adapter.rb, line 170
def create_tenant_command(conn, tenant)
  conn.create_database(environmentify(tenant), @config)
end
db_connection_config(tenant) click to toggle source
# File lib/apartment/adapters/abstract_adapter.rb, line 238
def db_connection_config(tenant)
  Apartment.db_config_for(tenant).dup
end
drop_command(conn, tenant) click to toggle source
# File lib/apartment/adapters/abstract_adapter.rb, line 153
def drop_command(conn, tenant)
  # connection.drop_database   note that drop_database will not throw an exception, so manually execute
  conn.execute("DROP DATABASE #{conn.quote_table_name(environmentify(tenant))}")
end
import_database_schema() click to toggle source

Import the database schema

# File lib/apartment/adapters/abstract_adapter.rb, line 194
def import_database_schema
  ActiveRecord::Schema.verbose = false # do not log schema load output.

  load_or_raise(Apartment.database_schema_file) if Apartment.database_schema_file
end
load_or_abort(file)

Backward compatibility

Alias for: load_or_raise
load_or_raise(file) click to toggle source

Load a file or raise error if it doesn't exists

# File lib/apartment/adapters/abstract_adapter.rb, line 218
def load_or_raise(file)
  raise FileNotFound, "#{file} doesn't exist yet" unless File.exist?(file)

  load(file)
end
Also aliased as: load_or_abort
multi_tenantify(tenant, with_database = true) click to toggle source
Return a new config that is multi-tenanted
@param {String}  tenant: Database name
@param {Boolean} with_database: if true, use the actual tenant's db name
                                if false, use the default db name from the db

rubocop:disable Style/OptionalBooleanParameter

# File lib/apartment/adapters/abstract_adapter.rb, line 205
def multi_tenantify(tenant, with_database = true)
  db_connection_config(tenant).tap do |config|
    multi_tenantify_with_tenant_db_name(config, tenant) if with_database
  end
end
multi_tenantify_with_tenant_db_name(config, tenant) click to toggle source

rubocop:enable Style/OptionalBooleanParameter

# File lib/apartment/adapters/abstract_adapter.rb, line 212
def multi_tenantify_with_tenant_db_name(config, tenant)
  config[:database] = environmentify(tenant)
end
process_excluded_model(excluded_model) click to toggle source
# File lib/apartment/adapters/abstract_adapter.rb, line 149
def process_excluded_model(excluded_model)
  excluded_model.constantize.establish_connection @config
end
raise_connect_error!(tenant, exception) click to toggle source
# File lib/apartment/adapters/abstract_adapter.rb, line 267
def raise_connect_error!(tenant, exception)
  raise TenantNotFound, "Error while connecting to tenant #{environmentify(tenant)}: #{exception.message}"
end
raise_create_tenant_error!(tenant, exception) click to toggle source
# File lib/apartment/adapters/abstract_adapter.rb, line 263
def raise_create_tenant_error!(tenant, exception)
  raise TenantExists, "Error while creating tenant #{environmentify(tenant)}: #{exception.message}"
end
raise_drop_tenant_error!(tenant, exception) click to toggle source
# File lib/apartment/adapters/abstract_adapter.rb, line 259
def raise_drop_tenant_error!(tenant, exception)
  raise TenantNotFound, "Error while dropping tenant #{environmentify(tenant)}: #{exception.message}"
end
rescuable_exceptions() click to toggle source

Exceptions to rescue from on db operations

# File lib/apartment/adapters/abstract_adapter.rb, line 228
def rescuable_exceptions
  [ActiveRecord::ActiveRecordError] + Array(rescue_from)
end
rescue_from() click to toggle source

Extra exceptions to rescue from

# File lib/apartment/adapters/abstract_adapter.rb, line 234
def rescue_from
  []
end
reset_on_connection_exception?() click to toggle source
# File lib/apartment/adapters/abstract_adapter.rb, line 255
def reset_on_connection_exception?
  false
end
with_neutral_connection(tenant) { |connection| ... } click to toggle source
# File lib/apartment/adapters/abstract_adapter.rb, line 242
def with_neutral_connection(tenant, &_block)
  if Apartment.with_multi_server_setup
    # neutral connection is necessary whenever you need to create/remove a database from a server.
    # example: when you use postgresql, you need to connect to the default postgresql database before you create
    # your own.
    SeparateDbConnectionHandler.establish_connection(multi_tenantify(tenant, false))
    yield(SeparateDbConnectionHandler.connection)
    SeparateDbConnectionHandler.connection.close
  else
    yield(Apartment.connection)
  end
end