Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix sequence name #187

Merged
merged 14 commits into from
Feb 3, 2022
Merged
1 change: 1 addition & 0 deletions lib/apartment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

require_relative 'apartment/log_subscriber'

require_relative 'apartment/active_record/postgresql_adapter'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would force the loading of the pg adapter. Are other dbs no longer supported? This was my comment on #143 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I have not looked at how can we fix this and test it on a non postgresql db. I currently run only postgres so it's also hard for me to test these details on all dbs. I've been tempted on starting refactoring the gem to have one gem per adapter and a core, but that's discussion for another day.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps we can go the "easy" route:

if defined?(PG)
  require_relative 'apartment/active_record/postgresql_adapter'
end

Perhaps in an initializer so that this happens after all gems are loaded?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think i have a clearer working solution. We only require the adapter once we eval the config. I'm requiring this patch after requiring the postgres adapter

if ActiveRecord.version.release >= Gem::Version.new('6.0')
require_relative 'apartment/active_record/connection_handling'
end
Expand Down
30 changes: 30 additions & 0 deletions lib/apartment/active_record/postgresql_adapter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 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}."

if res&.starts_with?(schema_prefix)
if Apartment.excluded_models.none? { |m| m.constantize.table_name == table }
rpbaltazar marked this conversation as resolved.
Show resolved Hide resolved
res.delete_prefix!(schema_prefix)
else
res.sub!(schema_prefix, "#{Apartment::Tenant.default_tenant}.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This causes extra work when default_tenant == current

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, this is not strictly correct, because it should happen even if the sequence name is prefixed with some other scheam. No idea if that can happen though 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought of that when i was writing this and then forgot to address it. tks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree w your second concern. addressed it as well

end
end
res
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
20 changes: 0 additions & 20 deletions lib/apartment/adapters/postgresql_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ def default_tenant
def reset
@current = default_tenant
Apartment.connection.schema_search_path = full_search_path
reset_sequence_names
end

def init
Expand Down Expand Up @@ -81,7 +80,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
Expand Down Expand Up @@ -130,24 +128,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

Expand Down
2 changes: 1 addition & 1 deletion spec/adapters/jdbc_mysql_adapter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand Down
2 changes: 1 addition & 1 deletion spec/adapters/jdbc_postgresql_adapter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
2 changes: 1 addition & 1 deletion spec/adapters/mysql2_adapter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand Down
2 changes: 1 addition & 1 deletion spec/adapters/postgresql_adapter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
2 changes: 1 addition & 1 deletion spec/adapters/sqlite3_adapter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
20 changes: 18 additions & 2 deletions spec/examples/schema_adapter_examples.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,19 @@
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
it 'should set the proper table_name on excluded_models' do
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
Expand Down Expand Up @@ -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"
rpbaltazar marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down