class Apartment::Adapters::PostgresqlSchemaAdapter

Separate Adapter for Postgresql when using schemas

Public Class Methods

new(config) click to toggle source
# File lib/apartment/adapters/postgresql_adapter.rb, line 27
def initialize(config)
  super

  reset
end

Public Instance Methods

current() click to toggle source
# File lib/apartment/adapters/postgresql_adapter.rb, line 52
def current
  @current || default_tenant
end
default_tenant() click to toggle source
# File lib/apartment/adapters/postgresql_adapter.rb, line 33
def default_tenant
  @default_tenant = Apartment.default_tenant || 'public'
end
init() click to toggle source
# File lib/apartment/adapters/postgresql_adapter.rb, line 47
def init
  super
  Apartment.connection.schema_search_path = full_search_path
end
reset() click to toggle source

Reset schema search path to the default schema_search_path

@return {String} default schema search path

# File lib/apartment/adapters/postgresql_adapter.rb, line 41
def reset
  @current = default_tenant
  Apartment.connection.schema_search_path = full_search_path
  reset_sequence_names
end

Protected Instance Methods

connect_to_new(tenant = nil) click to toggle source

Set schema search path to new schema

# File lib/apartment/adapters/postgresql_adapter.rb, line 73
def connect_to_new(tenant = nil)
  return reset if tenant.nil?
  raise ActiveRecord::StatementInvalid, "Could not find schema #{tenant}" unless schema_exists?(tenant)

  @current = tenant.is_a?(Array) ? tenant.map(&:to_s) : tenant.to_s
  Apartment.connection.schema_search_path = full_search_path

  # When the PostgreSQL version is < 9.3,
  # there is a issue for prepared statement with changing search_path.
  # https://www.postgresql.org/docs/9.3/static/sql-prepare.html
  Apartment.connection.clear_cache! if postgresql_version < 90_300
  reset_sequence_names
rescue *rescuable_exceptions
  raise TenantNotFound, "One of the following schema(s) is invalid: \"#{tenant}\" #{full_search_path}"
end
drop_command(conn, tenant) click to toggle source
# File lib/apartment/adapters/postgresql_adapter.rb, line 67
def drop_command(conn, tenant)
  conn.execute(%(DROP SCHEMA "#{tenant}" CASCADE))
end
process_excluded_model(excluded_model) click to toggle source
# File lib/apartment/adapters/postgresql_adapter.rb, line 58
def process_excluded_model(excluded_model)
  excluded_model.constantize.tap do |klass|
    # Ensure that if a schema *was* set, we override
    table_name = klass.table_name.split('.', 2).last

    klass.table_name = "#{default_tenant}.#{table_name}"
  end
end

Private Instance Methods

create_tenant_command(conn, tenant) click to toggle source
# File lib/apartment/adapters/postgresql_adapter.rb, line 97
def create_tenant_command(conn, tenant)
  # NOTE: This was causing some tests to fail because of the database strategy for rspec
  if ActiveRecord::Base.connection.open_transactions.positive?
    conn.execute(%(CREATE SCHEMA "#{tenant}"))
  else
    schema = %(BEGIN;
    CREATE SCHEMA "#{tenant}";
    COMMIT;)

    conn.execute(schema)
  end
rescue *rescuable_exceptions => e
  rollback_transaction(conn)
  raise e
end
full_search_path() click to toggle source

Generate the final search path to set including persistent_schemas

# File lib/apartment/adapters/postgresql_adapter.rb, line 119
def full_search_path
  persistent_schemas.map(&:inspect).join(', ')
end
persistent_schemas() click to toggle source
# File lib/apartment/adapters/postgresql_adapter.rb, line 123
def persistent_schemas
  [@current, Apartment.persistent_schemas].flatten
end
postgresql_version() click to toggle source
# File lib/apartment/adapters/postgresql_adapter.rb, line 127
def postgresql_version
  # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#postgresql_version is
  # public from Rails 5.0.
  Apartment.connection.send(:postgresql_version)
end
reset_sequence_names() click to toggle source
# File lib/apartment/adapters/postgresql_adapter.rb, line 133
def reset_sequence_names
  # sequence_name contains the schema, so it must be reset after switch
  # There is `reset_sequence_name`, but that method actually goes to the database
  # to find out the new name. Therefore, we do this hack to only unset the name,
  # and it will be dynamically found the next time it is needed
  descendants_to_unset = ActiveRecord::Base.descendants
                                           .select { |c| c.instance_variable_defined?(:@sequence_name) }
                                           .reject do |c|
                                             c.instance_variable_defined?(:@explicit_sequence_name) &&
                                               c.instance_variable_get(:@explicit_sequence_name)
                                           end
  descendants_to_unset.each do |c|
    # NOTE: due to this https://github.com/rails-on-services/apartment/issues/81
    # unreproduceable error we're checking before trying to remove it
    c.remove_instance_variable :@sequence_name if c.instance_variable_defined?(:@sequence_name)
  end
end
rollback_transaction(conn) click to toggle source
# File lib/apartment/adapters/postgresql_adapter.rb, line 113
def rollback_transaction(conn)
  conn.execute('ROLLBACK;')
end
schema_exists?(schemas) click to toggle source
# File lib/apartment/adapters/postgresql_adapter.rb, line 151
def schema_exists?(schemas)
  return true unless Apartment.tenant_presence_check

  Array(schemas).all? { |schema| Apartment.connection.schema_exists?(schema.to_s) }
end
tenant_exists?(tenant) click to toggle source
# File lib/apartment/adapters/postgresql_adapter.rb, line 91
def tenant_exists?(tenant)
  return true unless Apartment.tenant_presence_check

  Apartment.connection.schema_exists?(tenant)
end