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

Move SchemaMigration to an independent object #45908

Merged
merged 1 commit into from Sep 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions activerecord/CHANGELOG.md
@@ -1,3 +1,9 @@
* Move `ActiveRecord::SchemaMigration` to an independent object.

`ActiveRecord::SchemaMigration` no longer inherits from `ActiveRecord::Base` and is now an independent object that should be instantiated with a `connection`. This class is private and should not be used by applications directly. If you want to interact with the schema migrations table, please access it on the connection directly, for example: `ActiveRecord::Base.connection.schema_migration`.

*Eileen M. Uchitelle*

* Deprecate `all_connection_pools` and make `connection_pool_list` more explicit.

Following on #45924 `all_connection_pools` is now deprecated. `connection_pool_list` will either take an explicit role or applications can opt into the new behavior by passing `:all`.
Expand Down
Expand Up @@ -1270,7 +1270,7 @@ def check_constraint_exists?(table_name, **options)
end

def dump_schema_information # :nodoc:
versions = schema_migration.all_versions
versions = schema_migration.versions
insert_versions_sql(versions) if versions.any?
end

Expand Down
Expand Up @@ -193,21 +193,7 @@ def migration_context # :nodoc:
end

def schema_migration # :nodoc:
@schema_migration ||= begin
conn = self
connection_name = conn.pool.pool_config.connection_name

return ActiveRecord::SchemaMigration if connection_name == "ActiveRecord::Base"

schema_migration_name = "#{connection_name}::SchemaMigration"

Class.new(ActiveRecord::SchemaMigration) do
define_singleton_method(:name) { schema_migration_name }
define_singleton_method(:to_s) { schema_migration_name }

self.connection_specification_name = connection_name
end
end
SchemaMigration.new(self)
end

def internal_metadata # :nodoc:
Expand Down
35 changes: 21 additions & 14 deletions activerecord/lib/active_record/migration.rb
Expand Up @@ -950,14 +950,13 @@ def method_missing(method, *arguments, &block)

def copy(destination, sources, options = {})
copied = []
schema_migration = options[:schema_migration] || ActiveRecord::SchemaMigration

FileUtils.mkdir_p(destination) unless File.exist?(destination)

destination_migrations = ActiveRecord::MigrationContext.new(destination, schema_migration).migrations
destination_migrations = ActiveRecord::MigrationContext.new(destination, SchemaMigration::NullSchemaMigration.new).migrations
last = destination_migrations.last
sources.each do |scope, path|
source_migrations = ActiveRecord::MigrationContext.new(path, schema_migration).migrations
source_migrations = ActiveRecord::MigrationContext.new(path, SchemaMigration::NullSchemaMigration.new).migrations

source_migrations.each do |migration|
source = File.binread(migration.filename)
Expand Down Expand Up @@ -1018,7 +1017,7 @@ def next_migration_number(number)
if ActiveRecord.timestamped_migrations
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max
else
SchemaMigration.normalize_migration_number(number)
"%.3d" % number.to_i
end
end

Expand Down Expand Up @@ -1076,15 +1075,21 @@ def load_migration
#
# A migration context requires the path to the migrations is set
# in the +migrations_paths+ parameter. Optionally a +schema_migration+
# class can be provided. For most applications, +SchemaMigration+ is
# sufficient. Multiple database applications need a +SchemaMigration+
# per primary database.
# class can be provided. Multiple database applications will instantiate
# a +SchemaMigration+ object per database. From the Rake tasks, Rails will
# handle this for you.
class MigrationContext
attr_reader :migrations_paths, :schema_migration, :internal_metadata

def initialize(migrations_paths, schema_migration = SchemaMigration, internal_metadata = InternalMetadata)
def initialize(migrations_paths, schema_migration = nil, internal_metadata = InternalMetadata)
if schema_migration == SchemaMigration
schema_migration = SchemaMigration.new(ActiveRecord::Base.connection)

ActiveSupport::Deprecation.warn("SchemaMigration no longer inherits from ActiveRecord::Base. Please instaniate a new SchemaMigration object with the desired connection, ie `ActiveRecord::SchemaMigration.new(ActiveRecord::Base.connection)`.")
end

@migrations_paths = migrations_paths
@schema_migration = schema_migration
@schema_migration = schema_migration || SchemaMigration.new(ActiveRecord::Base.connection)
@internal_metadata = internal_metadata
end

Expand Down Expand Up @@ -1152,7 +1157,7 @@ def open # :nodoc:

def get_all_versions # :nodoc:
if schema_migration.table_exists?
schema_migration.all_versions.map(&:to_i)
schema_migration.integer_versions
else
[]
end
Expand Down Expand Up @@ -1257,7 +1262,9 @@ class << self

# For cases where a table doesn't exist like loading from schema cache
def current_version
MigrationContext.new(migrations_paths, SchemaMigration, InternalMetadata).current_version
schema_migration = SchemaMigration.new(ActiveRecord::Base.connection)

MigrationContext.new(migrations_paths, schema_migration, InternalMetadata).current_version
end
end

Expand Down Expand Up @@ -1327,7 +1334,7 @@ def migrated
end

def load_migrated
@migrated_versions = Set.new(@schema_migration.all_versions.map(&:to_i))
@migrated_versions = Set.new(@schema_migration.integer_versions)
end

private
Expand Down Expand Up @@ -1406,10 +1413,10 @@ def validate(migrations)
def record_version_state_after_migrating(version)
if down?
migrated.delete(version)
@schema_migration.delete_by(version: version.to_s)
@schema_migration.delete_version(version.to_s)
else
migrated << version
@schema_migration.create!(version: version.to_s)
@schema_migration.create_version(version.to_s)
end
end

Expand Down
99 changes: 66 additions & 33 deletions activerecord/lib/active_record/schema_migration.rb
@@ -1,54 +1,87 @@
# frozen_string_literal: true

require "active_record/scoping/default"
require "active_record/scoping/named"

module ActiveRecord
# This class is used to create a table that keeps track of which migrations
# have been applied to a given database. When a migration is run, its schema
# number is inserted in to the `SchemaMigration.table_name` so it doesn't need
# number is inserted in to the schema migrations table so it doesn't need
# to be executed the next time.
class SchemaMigration < ActiveRecord::Base # :nodoc:
class << self
def primary_key
"version"
end
class SchemaMigration # :nodoc:
attr_reader :connection, :arel_table

def initialize(connection)
@connection = connection
@arel_table = Arel::Table.new(table_name)
end

def create_version(version)
im = Arel::InsertManager.new(arel_table)
im.insert(arel_table[primary_key] => version)
connection.insert(im, "#{self} Create", primary_key, version)
end

def delete_version(version)
dm = Arel::DeleteManager.new(arel_table)
dm.wheres = [arel_table[primary_key].eq(version)]

def table_name
"#{table_name_prefix}#{schema_migrations_table_name}#{table_name_suffix}"
connection.delete(dm, "#{self} Destroy")
end

def delete_all_versions
versions.each do |version|
delete_version(version)
end
end

def primary_key
"version"
end

def table_name
"#{ActiveRecord::Base.table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{ActiveRecord::Base.table_name_suffix}"
end

def create_table
unless connection.table_exists?(table_name)
connection.create_table(table_name, id: false) do |t|
t.string :version, **connection.internal_string_options_for_primary_key
end
def create_table
unless connection.table_exists?(table_name)
connection.create_table(table_name, id: false) do |t|
t.string :version, **connection.internal_string_options_for_primary_key
end
end
end

def drop_table
connection.drop_table table_name, if_exists: true
end
def drop_table
connection.drop_table table_name, if_exists: true
end

def normalize_migration_number(number)
"%.3d" % number.to_i
end
def normalize_migration_number(number)
"%.3d" % number.to_i
end

def normalized_versions
all_versions.map { |v| normalize_migration_number v }
end
def normalized_versions
versions.map { |v| normalize_migration_number v }
end

def all_versions
order(:version).pluck(:version)
end
def versions
sm = Arel::SelectManager.new(arel_table)
sm.project(arel_table[primary_key])
sm.order(arel_table[primary_key].asc)
connection.select_values(sm)
end

def table_exists?
connection.data_source_exists?(table_name)
end
def integer_versions
versions.map(&:to_i)
end

def count
sm = Arel::SelectManager.new(arel_table)
sm.project(*Arel::Nodes::Count.new([Arel.star]))
connection.select_values(sm).first
end

def table_exists?
connection.data_source_exists?(table_name)
end

def version
super.to_i
class NullSchemaMigration
end
end
end
2 changes: 1 addition & 1 deletion activerecord/lib/active_record/tasks/database_tasks.rb
Expand Up @@ -192,7 +192,7 @@ def prepare_all
ActiveRecord::Base.establish_connection(db_config)

begin
database_initialized = ActiveRecord::SchemaMigration.table_exists?
database_initialized = ActiveRecord::Base.connection.schema_migration.table_exists?
rescue ActiveRecord::NoDatabaseError
create(db_config)
retry
Expand Down
6 changes: 2 additions & 4 deletions activerecord/test/cases/active_record_schema_test.rb
Expand Up @@ -20,7 +20,7 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
@connection.drop_table :nep_schema_migrations rescue nil
@connection.drop_table :has_timestamps rescue nil
@connection.drop_table :multiple_indexes rescue nil
@schema_migration.delete_all rescue nil
@schema_migration.delete_all_versions rescue nil
ActiveRecord::Migration.verbose = @original_verbose
end

Expand All @@ -31,7 +31,7 @@ def test_has_primary_key

@schema_migration.create_table
assert_difference "@schema_migration.count", 1 do
@schema_migration.create version: 12
@schema_migration.create_version(12)
end
ensure
@schema_migration.drop_table
Expand Down Expand Up @@ -69,7 +69,6 @@ def test_schema_define
def test_schema_define_with_table_name_prefix
old_table_name_prefix = ActiveRecord::Base.table_name_prefix
ActiveRecord::Base.table_name_prefix = "nep_"
@schema_migration.reset_table_name
@internal_metadata.reset_table_name
ActiveRecord::Schema.define(version: 7) do
create_table :fruits do |t|
Expand All @@ -82,7 +81,6 @@ def test_schema_define_with_table_name_prefix
assert_equal 7, @connection.migration_context.current_version
ensure
ActiveRecord::Base.table_name_prefix = old_table_name_prefix
@schema_migration.reset_table_name
@internal_metadata.reset_table_name
end

Expand Down
Expand Up @@ -5,6 +5,10 @@
class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
self.use_transactional_tests = false

def setup
@schema_migration = ActiveRecord::Base.connection.schema_migration
end

def test_renaming_index_on_foreign_key
connection.add_index "engines", "car_id"
connection.add_foreign_key :engines, :cars, name: "fk_engines_cars"
Expand All @@ -17,10 +21,10 @@ def test_renaming_index_on_foreign_key

def test_initializes_schema_migrations_for_encoding_utf8mb4
with_encoding_utf8mb4 do
table_name = ActiveRecord::SchemaMigration.table_name
table_name = @schema_migration.table_name
connection.drop_table table_name, if_exists: true

ActiveRecord::SchemaMigration.create_table
@schema_migration.create_table

assert connection.column_exists?(table_name, :version, :string)
end
Expand Down
Expand Up @@ -94,7 +94,7 @@ def teardown
ActiveRecord::Base.logger = @logger_was
ActiveRecord::Migration.verbose = @verbose_was
ActiveRecord::Base.connection.drop_table "mysql_table_options", if_exists: true
ActiveRecord::SchemaMigration.delete_all rescue nil
ActiveRecord::Base.connection.schema_migration.delete_all_versions rescue nil
end

test "new migrations do not contain default ENGINE=InnoDB option" do
Expand Down
Expand Up @@ -27,20 +27,18 @@ def setup

ActiveRecord::Base.table_name_prefix = "p_"
ActiveRecord::Base.table_name_suffix = "_s"
@connection.schema_migration.reset_table_name
@connection.internal_metadata.reset_table_name

@connection.schema_migration.delete_all rescue nil
@connection.schema_migration.delete_all_versions rescue nil
ActiveRecord::Migration.verbose = false
end

def teardown
@connection.schema_migration.delete_all rescue nil
@connection.schema_migration.delete_all_versions rescue nil
ActiveRecord::Migration.verbose = true

ActiveRecord::Base.table_name_prefix = @old_table_name_prefix
ActiveRecord::Base.table_name_suffix = @old_table_name_suffix
@connection.schema_migration.reset_table_name
@connection.internal_metadata.reset_table_name

super
Expand Down
4 changes: 2 additions & 2 deletions activerecord/test/cases/adapters/postgresql/uuid_test.rb
Expand Up @@ -308,7 +308,7 @@ def migrate(x)
ensure
drop_table "pg_uuids_4"
ActiveRecord::Migration.verbose = @verbose_was
ActiveRecord::Base.connection.schema_migration.delete_all
ActiveRecord::Base.connection.schema_migration.delete_all_versions
end
uses_transaction :test_schema_dumper_for_uuid_primary_key_default_in_legacy_migration
end
Expand Down Expand Up @@ -360,7 +360,7 @@ def migrate(x)
ensure
drop_table "pg_uuids_4"
ActiveRecord::Migration.verbose = @verbose_was
ActiveRecord::Base.connection.schema_migration.delete_all
ActiveRecord::Base.connection.schema_migration.delete_all_versions
end
uses_transaction :test_schema_dumper_for_uuid_primary_key_with_default_nil_in_legacy_migration
end
Expand Down