class Apartment::Adapters::AbstractAdapter
Abstract adapter from which all the Apartment
DB related adapters will inherit the base logic
Attributes
Public Class Methods
@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 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
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
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 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
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
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
Initialize Apartment
config options such as excluded_models
# File lib/apartment/adapters/abstract_adapter.rb, line 40 def init process_excluded_models end
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 the tenant connection to the default
# File lib/apartment/adapters/abstract_adapter.rb, line 118 def reset Apartment.establish_connection @config end
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
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 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
@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 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
# File lib/apartment/adapters/abstract_adapter.rb, line 170 def create_tenant_command(conn, tenant) conn.create_database(environmentify(tenant), @config) end
# File lib/apartment/adapters/abstract_adapter.rb, line 238 def db_connection_config(tenant) Apartment.db_config_for(tenant).dup end
# 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 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 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
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
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
# File lib/apartment/adapters/abstract_adapter.rb, line 149 def process_excluded_model(excluded_model) excluded_model.constantize.establish_connection @config end
# 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
# 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
# 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
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
Extra exceptions to rescue from
# File lib/apartment/adapters/abstract_adapter.rb, line 234 def rescue_from [] end
# File lib/apartment/adapters/abstract_adapter.rb, line 255 def reset_on_connection_exception? false end
# 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