diff --git a/CHANGELOG.md b/CHANGELOG.md index 345c2ba..db1b4be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## master (unreleased) +- Add `ensure_background_migration_succeeded` migration helper for background migrations + ## 0.14.0 (2024-02-01) - Add ability to configure whether background migrations should be run inline diff --git a/docs/background_migrations.md b/docs/background_migrations.md index eaee92e..fd4d689 100644 --- a/docs/background_migrations.md +++ b/docs/background_migrations.md @@ -125,6 +125,10 @@ enqueue_background_migration("MyMigrationWithArgs", arg1, arg2, ...) **Note**: These migration helpers should be run inside the migration against the database where background migrations tables are defined. +## Depending on migrated data + +You shouldn't depend on the data until the background migration is finished. If having 100% of the data migrated is a requirement, then the `ensure_background_migration_succeeded` helper can be used to guarantee that the migration was succeeded and the data fully migrated. + ## Testing At a minimum, it's recommended that the `#process_batch` method in your background migration is tested. You may also want to test the `#relation` and `#count` methods if they are sufficiently complex. diff --git a/lib/online_migrations/background_migrations/migration.rb b/lib/online_migrations/background_migrations/migration.rb index a04b7dd..b3f0514 100644 --- a/lib/online_migrations/background_migrations/migration.rb +++ b/lib/online_migrations/background_migrations/migration.rb @@ -20,6 +20,7 @@ class Migration < ApplicationRecord self.table_name = :background_migrations scope :queue_order, -> { order(created_at: :asc) } + scope :parents, -> { where(parent_id: nil) } scope :runnable, -> { where(composite: false) } scope :active, -> { where(status: [statuses[:enqueued], statuses[:running]]) } scope :except_succeeded, -> { where.not(status: :succeeded) } diff --git a/lib/online_migrations/background_migrations/migration_helpers.rb b/lib/online_migrations/background_migrations/migration_helpers.rb index 7b7c96d..c27b577 100644 --- a/lib/online_migrations/background_migrations/migration_helpers.rb +++ b/lib/online_migrations/background_migrations/migration_helpers.rb @@ -381,6 +381,21 @@ def enqueue_background_migration(migration_name, *arguments, **options) migration end + # Ensures that the background migration with provided configuration is succeeded. + # + # If the enqueued migration was not found (probably when resetting a dev environment + # followed by `db:migrate`) then a log warning is printed. + # If enqueued migration was found but is not succeeded, then the error is raised. + # + # @param migration_name [String, Class] Background migration job class name + # @param arguments [Array, nil] Arguments with which background migration was enqueued + # + # @example Without arguments + # ensure_background_migration_succeeded("BackfillProjectIssuesCount") + # + # @example With arguments + # ensure_background_migration_succeeded("CopyColumn", arguments: ["users", "id", "id_for_type_change"]) + # def ensure_background_migration_succeeded(migration_name, arguments: nil) migration_name = migration_name.name if migration_name.is_a?(Class) @@ -388,16 +403,16 @@ def ensure_background_migration_succeeded(migration_name, arguments: nil) if arguments arguments = Array(arguments) - migration = Migration.for_migration_name(migration_name).first + migration = Migration.parents.for_configuration(migration_name, arguments).first configuration[:arguments] = arguments.to_json else - migration = Migration.for_migration_name(migration_name).first + migration = Migration.parents.for_migration_name(migration_name).first end if migration.nil? Utils.say("Could not find background migration for the given configuration: #{configuration}") elsif !migration.succeeded? - raise "Expected background migration for the given configuration to be marked as 'succeeded', "\ + raise "Expected background migration for the given configuration to be marked as 'succeeded', " \ "but it is '#{migration.status}': #{configuration}" end end diff --git a/test/background_migrations/background_migrations.rb b/test/background_migrations/background_migrations.rb index c5c5e48..2c0eeca 100644 --- a/test/background_migrations/background_migrations.rb +++ b/test/background_migrations/background_migrations.rb @@ -24,6 +24,8 @@ class Dog < ShardRecord end class MakeAllNonAdmins < OnlineMigrations::BackgroundMigration + def initialize(*_dummy_args) end + def relation User.all end diff --git a/test/schema_statements/misc_test.rb b/test/schema_statements/misc_test.rb index 1c54ab4..d881c79 100644 --- a/test/schema_statements/misc_test.rb +++ b/test/schema_statements/misc_test.rb @@ -206,6 +206,37 @@ def test_run_background_migrations_inline_configured_to_custom_proc def test_ensure_background_migration_succeeded m = @connection.enqueue_background_migration("MakeAllNonAdmins") assert m.succeeded? + assert_nothing_raised do + @connection.ensure_background_migration_succeeded("MakeAllNonAdmins") + end + end + + def test_ensure_background_migration_succeeded_with_arguments + m = @connection.enqueue_background_migration("MakeAllNonAdmins", 1, { foo: "bar" }) + assert m.succeeded? + assert_nothing_raised do + @connection.ensure_background_migration_succeeded("MakeAllNonAdmins", arguments: [1, { foo: "bar" }]) + end + end + + def test_ensure_background_migration_succeeded_when_migration_not_found + out, = capture_io do + logger = ActiveSupport::Logger.new($stdout) + ActiveRecord::Base.stub(:logger, logger) do + @connection.ensure_background_migration_succeeded("MakeAllNonAdmins") + end + end + assert_match(/Could not find background migration/i, out) + end + + def test_ensure_background_migration_succeeded_when_migration_is_failed + m = @connection.enqueue_background_migration("MakeAllNonAdmins") + m.update_column(:status, :failed) + + error = assert_raises(RuntimeError) do + @connection.ensure_background_migration_succeeded("MakeAllNonAdmins") + end + assert_match(/to be marked as 'succeeded'/i, error.message) end def test_disable_statement_timeout