diff --git a/lib/apartment/active_record/postgresql_adapter.rb b/lib/apartment/active_record/postgresql_adapter.rb new file mode 100644 index 00000000..ef878111 --- /dev/null +++ b/lib/apartment/active_record/postgresql_adapter.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +# rubocop:disable Style/ClassAndModuleChildren + +# NOTE: This patch is meant to remove any schema_prefix appart from the ones for +# excluded models. The schema_prefix would be resolved by apartment's setting +# of search path +module Apartment::PostgreSqlAdapterPatch + def default_sequence_name(table, _column) + res = super + schema_prefix = "#{Apartment::Tenant.current}." + default_tenant_prefix = "#{Apartment::Tenant.default_tenant}." + + # NOTE: Excluded models should always access the sequence from the default + # tenant schema + if excluded_model?(table) + res.sub!(schema_prefix, default_tenant_prefix) if schema_prefix != default_tenant_prefix + return res + end + + res.delete_prefix!(schema_prefix) if res&.starts_with?(schema_prefix) + + res + end + + private + + def excluded_model?(table) + Apartment.excluded_models.any? { |m| m.constantize.table_name == table } + end +end + +require 'active_record/connection_adapters/postgresql_adapter' + +# NOTE: inject this into postgresql adapters +class ActiveRecord::ConnectionAdapters::PostgreSQLAdapter + include Apartment::PostgreSqlAdapterPatch +end +# rubocop:enable Style/ClassAndModuleChildren diff --git a/lib/apartment/adapters/postgresql_adapter.rb b/lib/apartment/adapters/postgresql_adapter.rb index 6e9707de..7b85aa51 100644 --- a/lib/apartment/adapters/postgresql_adapter.rb +++ b/lib/apartment/adapters/postgresql_adapter.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'apartment/adapters/abstract_adapter' +require 'apartment/active_record/postgresql_adapter' module Apartment module Tenant @@ -41,7 +42,6 @@ def default_tenant def reset @current = default_tenant Apartment.connection.schema_search_path = full_search_path - reset_sequence_names end def init @@ -81,7 +81,6 @@ def connect_to_new(tenant = nil) # 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 => e raise_schema_connect_to_new(tenant, e) end @@ -130,24 +129,6 @@ def postgresql_version Apartment.connection.send(:postgresql_version) end - 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 - def schema_exists?(schemas) return true unless Apartment.tenant_presence_check diff --git a/spec/adapters/jdbc_mysql_adapter_spec.rb b/spec/adapters/jdbc_mysql_adapter_spec.rb index b757c96b..de9c3860 100644 --- a/spec/adapters/jdbc_mysql_adapter_spec.rb +++ b/spec/adapters/jdbc_mysql_adapter_spec.rb @@ -6,7 +6,7 @@ require 'apartment/adapters/jdbc_mysql_adapter' describe Apartment::Adapters::JDBCMysqlAdapter, database: :mysql do - subject { Apartment::Tenant.jdbc_mysql_adapter config.symbolize_keys } + subject(:adapter) { Apartment::Tenant.adapter } def tenant_names ActiveRecord::Base.connection.execute('SELECT schema_name FROM information_schema.schemata').collect do |row| diff --git a/spec/adapters/jdbc_postgresql_adapter_spec.rb b/spec/adapters/jdbc_postgresql_adapter_spec.rb index d1deabf3..4db0eb8b 100644 --- a/spec/adapters/jdbc_postgresql_adapter_spec.rb +++ b/spec/adapters/jdbc_postgresql_adapter_spec.rb @@ -6,7 +6,7 @@ require 'apartment/adapters/jdbc_postgresql_adapter' describe Apartment::Adapters::JDBCPostgresqlAdapter, database: :postgresql do - subject { Apartment::Tenant.jdbc_postgresql_adapter config.symbolize_keys } + subject(:adapter) { Apartment::Tenant.adapter } it_behaves_like 'a generic apartment adapter callbacks' diff --git a/spec/adapters/mysql2_adapter_spec.rb b/spec/adapters/mysql2_adapter_spec.rb index fea994f4..6ff7c56c 100644 --- a/spec/adapters/mysql2_adapter_spec.rb +++ b/spec/adapters/mysql2_adapter_spec.rb @@ -6,7 +6,7 @@ describe Apartment::Adapters::Mysql2Adapter, database: :mysql do unless defined?(JRUBY_VERSION) - subject(:adapter) { Apartment::Tenant.mysql2_adapter config } + subject(:adapter) { Apartment::Tenant.adapter } def tenant_names ActiveRecord::Base.connection.execute('SELECT schema_name FROM information_schema.schemata').collect do |row| diff --git a/spec/adapters/postgresql_adapter_spec.rb b/spec/adapters/postgresql_adapter_spec.rb index 7944c862..981440f6 100644 --- a/spec/adapters/postgresql_adapter_spec.rb +++ b/spec/adapters/postgresql_adapter_spec.rb @@ -6,7 +6,7 @@ describe Apartment::Adapters::PostgresqlAdapter, database: :postgresql do unless defined?(JRUBY_VERSION) - subject { Apartment::Tenant.postgresql_adapter config } + subject { Apartment::Tenant.adapter } it_behaves_like 'a generic apartment adapter callbacks' diff --git a/spec/adapters/sqlite3_adapter_spec.rb b/spec/adapters/sqlite3_adapter_spec.rb index 1581a3e3..339cb38c 100644 --- a/spec/adapters/sqlite3_adapter_spec.rb +++ b/spec/adapters/sqlite3_adapter_spec.rb @@ -6,7 +6,7 @@ describe Apartment::Adapters::Sqlite3Adapter, database: :sqlite do unless defined?(JRUBY_VERSION) - subject { Apartment::Tenant.sqlite3_adapter config } + subject(:adapter) { Apartment::Tenant.adapter } it_behaves_like 'a generic apartment adapter callbacks' diff --git a/spec/examples/schema_adapter_examples.rb b/spec/examples/schema_adapter_examples.rb index 586dce62..70c71504 100644 --- a/spec/examples/schema_adapter_examples.rb +++ b/spec/examples/schema_adapter_examples.rb @@ -27,6 +27,9 @@ Apartment::Tenant.init expect(Company.table_name).to eq('public.companies') + expect(Company.sequence_name).to eq('public.companies_id_seq') + expect(User.table_name).to eq('users') + expect(User.sequence_name).to eq('users_id_seq') end context 'with a default_tenant', default_tenant: true do @@ -34,6 +37,9 @@ Apartment::Tenant.init expect(Company.table_name).to eq("#{default_tenant}.companies") + expect(Company.sequence_name).to eq("#{default_tenant}.companies_id_seq") + expect(User.table_name).to eq('users') + expect(User.sequence_name).to eq('users_id_seq') end it 'sets the search_path correctly' do @@ -116,15 +122,25 @@ end describe '#switch' do + before do + Apartment.configure do |config| + config.excluded_models = ['Company'] + end + end + + # rubocop:disable RSpec/MultipleExpectations it 'connects and resets' do subject.switch(schema1) do expect(connection.schema_search_path).to start_with %("#{schema1}") - expect(User.sequence_name).to eq "#{schema1}.#{User.table_name}_id_seq" + expect(User.sequence_name).to eq "#{User.table_name}_id_seq" + expect(Company.sequence_name).to eq "#{public_schema}.#{Company.table_name}_id_seq" end expect(connection.schema_search_path).to start_with %("#{public_schema}") - expect(User.sequence_name).to eq "#{public_schema}.#{User.table_name}_id_seq" + expect(User.sequence_name).to eq "#{User.table_name}_id_seq" + expect(Company.sequence_name).to eq "#{public_schema}.#{Company.table_name}_id_seq" end + # rubocop:enable RSpec/MultipleExpectations it 'allows a list of schemas' do subject.switch([schema1, schema2]) do