diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 404bd3ad..40b232ad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,6 +17,7 @@ jobs: gemfile: - gemfiles/rails_6.1.gemfile - gemfiles/rails_7.0.gemfile + - gemfiles/rails_7.1.0.alpha.gemfile runs-on: ubuntu-latest env: BUNDLE_GEMFILE: ${{ matrix.gemfile }} diff --git a/Appraisals b/Appraisals index 9bb5a4ba..f8722491 100644 --- a/Appraisals +++ b/Appraisals @@ -5,3 +5,7 @@ end appraise 'rails-7.0' do gem 'rails', '~> 7.0' end + +appraise 'rails-7.1.0.alpha' do + gem 'rails', git: "https://github.com/rails/rails.git", branch: "main" +end diff --git a/gemfiles/rails_7.1.0.alpha.gemfile b/gemfiles/rails_7.1.0.alpha.gemfile new file mode 100644 index 00000000..bcccc8a3 --- /dev/null +++ b/gemfiles/rails_7.1.0.alpha.gemfile @@ -0,0 +1,8 @@ +# This file was generated by Appraisal + +source "http://rubygems.org" + +gem "sqlite3", "~> 1.4" +gem "rails", git: "https://github.com/rails/rails.git", branch: "main" + +gemspec path: "../" diff --git a/lib/data_migrate.rb b/lib/data_migrate.rb index d78eed37..afebb199 100644 --- a/lib/data_migrate.rb +++ b/lib/data_migrate.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -require File.join(File.dirname(__FILE__), "data_migrate", "data_migrator") -require File.join(File.dirname(__FILE__), "data_migrate", "data_schema_migration") +require 'active_record' require File.join(File.dirname(__FILE__), "data_migrate", "data_schema") require File.join(File.dirname(__FILE__), "data_migrate", "database_tasks") require File.join(File.dirname(__FILE__), "data_migrate", "schema_dumper") @@ -9,9 +8,17 @@ require File.join(File.dirname(__FILE__), "data_migrate", "migration_context") require File.join(File.dirname(__FILE__), "data_migrate", "railtie") require File.join(File.dirname(__FILE__), "data_migrate", "tasks/data_migrate_tasks") -require File.join(File.dirname(__FILE__), "data_migrate", "legacy_migrator") require File.join(File.dirname(__FILE__), "data_migrate", "config") -require File.join(File.dirname(__FILE__), "data_migrate", "schema_migration") + +if Gem::Dependency.new("railties", "~> 7.1").match?("railties", Gem.loaded_specs["railties"].version) + require File.join(File.dirname(__FILE__), "data_migrate", "schema_migration_seven_one") + require File.join(File.dirname(__FILE__), "data_migrate", "data_migrator_seven_one") + require File.join(File.dirname(__FILE__), "data_migrate", "data_schema_migration_seven_one") +else + require File.join(File.dirname(__FILE__), "data_migrate", "schema_migration") + require File.join(File.dirname(__FILE__), "data_migrate", "data_migrator") + require File.join(File.dirname(__FILE__), "data_migrate", "data_schema_migration") +end module DataMigrate def self.root diff --git a/lib/data_migrate/data_migrator_seven_one.rb b/lib/data_migrate/data_migrator_seven_one.rb new file mode 100644 index 00000000..028272b1 --- /dev/null +++ b/lib/data_migrate/data_migrator_seven_one.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require "data_migrate/config" + +module DataMigrate + class DataMigrator < ActiveRecord::Migrator + def self.migrations_paths + [DataMigrate.config.data_migrations_path] + end + + def self.assure_data_schema_table + ActiveRecord::Base.establish_connection(db_config) + DataMigrate::DataSchemaMigration.create_table + end + + def initialize(direction, migrations, target_version = nil) + @direction = direction + @target_version = target_version + @migrated_versions = nil + @migrations = migrations + @schema_migration = ActiveRecord::Base.connection.schema_migration + @internal_metadata = ActiveRecord::Base.connection.internal_metadata + + validate(@migrations) + + DataMigrate::DataSchemaMigration.create_table + @internal_metadata.create_table + end + + def load_migrated + @migrated_versions = + DataMigrate::DataSchemaMigration.normalized_versions.map(&:to_i).sort + end + + class << self + def current_version + DataMigrate::MigrationContext.new(migrations_paths).current_version + end + + ## + # Compares the given filename with what we expect data migration + # filenames to be, eg the "20091231235959_some_name.rb" pattern + # @param (String) filename + # @return (MatchData) + def match(filename) + /(\d{14})_(.+)\.rb$/.match(filename) + end + + def needs_migration? + DataMigrate::DatabaseTasks.pending_migrations.count.positive? + end + ## + # Provides the full migrations_path filepath + # @return (String) + def full_migrations_path + File.join(Rails.root, *migrations_paths.split(File::SEPARATOR)) + end + + def migrations_status + DataMigrate::MigrationContext.new(migrations_paths).migrations_status + end + + # TODO: this was added to be backward compatible, need to re-evaluate + def migrations(_migrations_paths) + #DataMigrate::MigrationContext.new(migrations_paths).migrations + DataMigrate::MigrationContext.new(_migrations_paths).migrations + end + + #TODO: this was added to be backward compatible, need to re-evaluate + def run(direction, migration_paths, version) + DataMigrate::MigrationContext.new(migration_paths).run(direction, version) + end + + def rollback(migrations_path, steps) + DataMigrate::MigrationContext.new(migrations_path).rollback(steps) + end + + def db_config + env = Rails.env || "development" + ar_config = ActiveRecord::Base.configurations.configs_for(env_name: env).first + ar_config || ENV["DATABASE_URL"] + end + end + + private + + def record_version_state_after_migrating(version) + if down? + migrated.delete(version) + DataMigrate::DataSchemaMigration.delete_version(version: version.to_s) + else + migrated << version + DataMigrate::DataSchemaMigration.create!(version: version.to_s) + end + end + end +end diff --git a/lib/data_migrate/data_schema_migration_seven_one.rb b/lib/data_migrate/data_schema_migration_seven_one.rb new file mode 100644 index 00000000..a0fd2858 --- /dev/null +++ b/lib/data_migrate/data_schema_migration_seven_one.rb @@ -0,0 +1,26 @@ +module DataMigrate + class DataSchemaMigration + class << self + delegate :table_name, :primary_key, :create_table, :normalized_versions, :create_version, :create!, :table_exists?, :exists?, :where, to: :instance + + def instance + @instance ||= Class.new(::ActiveRecord::SchemaMigration) do + define_method(:table_name) { ActiveRecord::Base.table_name_prefix + 'data_migrations' + ActiveRecord::Base.table_name_suffix } + define_method(:primary_key) { "version" } + end.new(ActiveRecord::Base.connection) + end + + def delete_version(version:) + instance.delete_version(version) + end + + def create(version:) + instance.create_version(version) + end + + def create!(version:) + instance.create_version(version) + end + end + end +end diff --git a/lib/data_migrate/schema_migration_seven_one.rb b/lib/data_migrate/schema_migration_seven_one.rb new file mode 100644 index 00000000..42d2d8c0 --- /dev/null +++ b/lib/data_migrate/schema_migration_seven_one.rb @@ -0,0 +1,61 @@ +module DataMigrate + # Helper class to getting access to db schema + # to allow data/schema combination tasks + class SchemaMigration + def self.pending_schema_migrations + all_migrations = DataMigrate::MigrationContext.new(migrations_paths).migrations + sort_migrations( + ActiveRecord::Migrator.new(:up, all_migrations, ActiveRecord::Base.connection.schema_migration, ActiveRecord::Base.connection.internal_metadata). + pending_migrations. + map {|m| { version: m.version, kind: :schema }} + ) + end + + def self.run(direction, migration_paths, version) + ActiveRecord::MigrationContext.new(migration_paths, ActiveRecord::Base.connection.schema_migration).run(direction, version) + end + + def self.sort_migrations(set1, set2 = nil) + migrations = set1 + (set2 || []) + migrations.sort {|a, b| sort_string(a) <=> sort_string(b)} + end + + def self.migrations_paths + spec_name = DataMigrate.config.spec_name + if spec_name + ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: spec_name).migrations_paths + else + Rails.application.config.paths["db/migrate"].to_a + end + end + + def self.sort_string(migration) + "#{migration[:version]}_#{migration[:kind] == :data ? 1 : 0}" + end + end +end + +# ActiveRecord::SchemaMigration no longer inherits from ActiveRecord::Base in Rails 7.1 +# and now has updated method names. See: https://github.com/rails/rails/pull/45908 +# This patch delegates SchemaMigration calls to the updated connection instance. +class ActiveRecord::SchemaMigration + class << self + delegate :create_table, :table_exists?, :normalized_versions, to: :schema_migration + + def schema_migration + ActiveRecord::Base.connection.schema_migration + end + end + + def self.schema_migrations_table_name + ActiveRecord::Base.connection.schema_migration.table_name + end + + def self.create(version:) + ActiveRecord::Base.connection.schema_migration.create_version(version) + end + + def self.normalize_migration_number(version) + ActiveRecord::Base.connection.schema_migration.normalize_migration_number(version) + end +end