From 5c3b59ee6ffda688675e820718960cb04ca88047 Mon Sep 17 00:00:00 2001 From: Mane Darbinyan Date: Mon, 9 Dec 2024 00:47:22 +0400 Subject: [PATCH 1/2] Add rollback initiation and error messages --- lib/actual_db_schema.rb | 1 + lib/actual_db_schema/commands/rollback.rb | 11 +--------- lib/actual_db_schema/output_formatter.rb | 18 ++++++++++++++++ .../patches/migration_context.rb | 21 +++++++++++++++++-- test/rake_task_test.rb | 3 +++ 5 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 lib/actual_db_schema/output_formatter.rb diff --git a/lib/actual_db_schema.rb b/lib/actual_db_schema.rb index 5426dcf..21cdf8a 100644 --- a/lib/actual_db_schema.rb +++ b/lib/actual_db_schema.rb @@ -9,6 +9,7 @@ require_relative "actual_db_schema/migration" require_relative "actual_db_schema/failed_migration" require_relative "actual_db_schema/migration_context" +require_relative "actual_db_schema/output_formatter" require_relative "actual_db_schema/patches/migration_proxy" require_relative "actual_db_schema/patches/migrator" require_relative "actual_db_schema/patches/migration_context" diff --git a/lib/actual_db_schema/commands/rollback.rb b/lib/actual_db_schema/commands/rollback.rb index 08a8f36..818afa3 100644 --- a/lib/actual_db_schema/commands/rollback.rb +++ b/lib/actual_db_schema/commands/rollback.rb @@ -4,11 +4,7 @@ module ActualDbSchema module Commands # Rolls back all phantom migrations class Rollback < Base - UNICODE_COLORS = { - red: 31, - green: 32, - yellow: 33 - }.freeze + include ActualDbSchema::OutputFormatter def initialize(context, manual_mode: false) @manual_mode = manual_mode || manual_mode_default? @@ -57,11 +53,6 @@ def puts_into def manual_mode_default? ActualDbSchema.config[:auto_rollback_disabled] end - - def colorize(text, color) - code = UNICODE_COLORS.fetch(color, 37) - "\e[#{code}m#{text}\e[0m" - end end end end diff --git a/lib/actual_db_schema/output_formatter.rb b/lib/actual_db_schema/output_formatter.rb new file mode 100644 index 0000000..be124c5 --- /dev/null +++ b/lib/actual_db_schema/output_formatter.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module ActualDbSchema + # Provides functionality for formatting terminal output with colors + module OutputFormatter + UNICODE_COLORS = { + red: 31, + green: 32, + yellow: 33, + gray: 90 + }.freeze + + def colorize(text, color) + code = UNICODE_COLORS.fetch(color, 37) + "\e[#{code}m#{text}\e[0m" + end + end +end diff --git a/lib/actual_db_schema/patches/migration_context.rb b/lib/actual_db_schema/patches/migration_context.rb index ba7817d..54e3e7a 100644 --- a/lib/actual_db_schema/patches/migration_context.rb +++ b/lib/actual_db_schema/patches/migration_context.rb @@ -4,6 +4,8 @@ module ActualDbSchema module Patches # Add new command to roll back the phantom migrations module MigrationContext + include ActualDbSchema::OutputFormatter + def rollback_branches(manual_mode: false) phantom_migrations.reverse_each do |migration| next unless status_up?(migration) @@ -11,7 +13,7 @@ def rollback_branches(manual_mode: false) show_info_for(migration) if manual_mode migrate(migration) if !manual_mode || user_wants_rollback? rescue StandardError => e - ActualDbSchema.failed << FailedMigration.new(migration: migration, exception: e) + handle_rollback_error(migration, e) end end @@ -63,7 +65,7 @@ def user_wants_rollback? end def show_info_for(migration) - puts "\n[ActualDbSchema] A phantom migration was found and is about to be rolled back." + puts colorize("\n[ActualDbSchema] A phantom migration was found and is about to be rolled back.", :gray) puts "Please make a decision from the options below to proceed.\n\n" puts "Branch: #{branch_for(migration.version.to_s)}" puts "Database: #{ActualDbSchema.db_config[:database]}" @@ -72,6 +74,10 @@ def show_info_for(migration) end def migrate(migration) + message = "[ActualDbSchema] Rolling back phantom migration #{migration.version} #{migration.name} " \ + "(from branch: #{branch_for(migration.version.to_s)})" + puts colorize(message, :gray) + migrator = down_migrator_for(migration) migrator.extend(ActualDbSchema::Patches::Migrator) migrator.migrate @@ -84,6 +90,17 @@ def branch_for(version) def metadata @metadata ||= ActualDbSchema::Store.instance.read end + + def handle_rollback_error(migration, exception) + error_message = <<~ERROR + Error encountered during rollback: + + #{exception.message.gsub(/^An error has occurred, all later migrations canceled:\s*/, "").strip} + ERROR + + puts colorize(error_message, :red) + ActualDbSchema.failed << FailedMigration.new(migration: migration, exception: exception) + end end end end diff --git a/test/rake_task_test.rb b/test/rake_task_test.rb index 351f01e..736c3ff 100644 --- a/test/rake_task_test.rb +++ b/test/rake_task_test.rb @@ -36,6 +36,7 @@ assert_empty TestingState.down utils.run_migrations assert_equal %i[second first], TestingState.down + assert_match(/\[ActualDbSchema\] Rolling back phantom migration/, TestingState.output) end describe "with irreversible migration" do @@ -59,6 +60,8 @@ def down assert_empty ActualDbSchema.failed utils.run_migrations assert_equal(%w[20130906111513_irreversible.rb], ActualDbSchema.failed.map { |m| File.basename(m.filename) }) + assert_match(/Error encountered during rollback:/, TestingState.output) + assert_match(/ActiveRecord::IrreversibleMigration/, TestingState.output) end end From 18081beb8fe8109531c3c45b770a261c30b6d82a Mon Sep 17 00:00:00 2001 From: Mane Darbinyan Date: Mon, 9 Dec 2024 00:53:56 +0400 Subject: [PATCH 2/2] Add success message and update error summary --- lib/actual_db_schema/commands/rollback.rb | 67 ++++++++++++------- lib/actual_db_schema/failed_migration.rb | 2 +- .../patches/migration_context.rb | 11 ++- test/rake_task_test.rb | 3 + 4 files changed, 57 insertions(+), 26 deletions(-) diff --git a/lib/actual_db_schema/commands/rollback.rb b/lib/actual_db_schema/commands/rollback.rb index 818afa3..be71319 100644 --- a/lib/actual_db_schema/commands/rollback.rb +++ b/lib/actual_db_schema/commands/rollback.rb @@ -5,6 +5,7 @@ module Commands # Rolls back all phantom migrations class Rollback < Base include ActualDbSchema::OutputFormatter + include ActionView::Helpers::TextHelper def initialize(context, manual_mode: false) @manual_mode = manual_mode || manual_mode_default? @@ -14,40 +15,58 @@ def initialize(context, manual_mode: false) private def call_impl - context.rollback_branches(manual_mode: @manual_mode) + rolled_back = context.rollback_branches(manual_mode: @manual_mode) - return if ActualDbSchema.failed.empty? + return unless rolled_back - puts_preamble - puts_into - puts "" - puts failed_migrations_list - puts_preamble + ActualDbSchema.failed.empty? ? print_success : print_error + end + + def print_success + puts colorize("[ActualDbSchema] All phantom migrations rolled back successfully! 🎉", :green) + end + + def print_error + header_message = <<~HEADER + #{ActualDbSchema.failed.count} phantom migration(s) could not be rolled back automatically. + + Try these steps to fix and move forward: + 1. Ensure the migrations are reversible (define #up and #down methods or use #reversible). + 2. If the migration references code or tables from another branch, restore or remove them. + 3. Once fixed, run `rails db:migrate` again. + + Below are the details of the problematic migrations: + HEADER + + print_error_summary("#{header_message}\n#{failed_migrations_list}") end def failed_migrations_list ActualDbSchema.failed.map.with_index(1) do |failed, index| - filename = failed.short_filename - exception = failed.exception - <<~MSG - \t#{colorize("[Migration##{index}]", :yellow)} - \t- #{filename} - - \t\t#{exception.inspect.gsub("\n", "\n\t ")} - MSG - end + <<~MIGRATION + #{colorize("Migration ##{index}:", :yellow)} + File: #{failed.short_filename} + Branch: #{failed.branch} + MIGRATION + end.join("\n") end - def puts_preamble - puts "" - puts %(\u2757\u2757\u2757 #{colorize("[ActualDbSchema]", :red)}) - puts "" + def print_error_summary(content) + width = 100 + indent = 4 + gem_name = "ActualDbSchema" + + puts colorize("╔═ [#{gem_name}] #{"═" * (width - gem_name.length - 5)}╗", :red) + print_wrapped_content(content, width, indent) + puts colorize("╚#{"═" * width}╝", :red) end - def puts_into - msg = "#{ActualDbSchema.failed.count} phantom migration(s) could not be rolled back automatically." - msg += " Roll them back or fix manually:" - puts colorize(msg, :red) + def print_wrapped_content(content, width, indent) + usable_width = width - indent - 4 + wrapped_content = word_wrap(content, line_width: usable_width) + wrapped_content.each_line do |line| + puts "#{" " * indent}#{line.chomp}" + end end def manual_mode_default? diff --git a/lib/actual_db_schema/failed_migration.rb b/lib/actual_db_schema/failed_migration.rb index 94390cd..2c27e3f 100644 --- a/lib/actual_db_schema/failed_migration.rb +++ b/lib/actual_db_schema/failed_migration.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module ActualDbSchema - FailedMigration = Struct.new(:migration, :exception, keyword_init: true) do + FailedMigration = Struct.new(:migration, :exception, :branch, keyword_init: true) do def filename migration.filename end diff --git a/lib/actual_db_schema/patches/migration_context.rb b/lib/actual_db_schema/patches/migration_context.rb index 54e3e7a..22808f0 100644 --- a/lib/actual_db_schema/patches/migration_context.rb +++ b/lib/actual_db_schema/patches/migration_context.rb @@ -7,14 +7,19 @@ module MigrationContext include ActualDbSchema::OutputFormatter def rollback_branches(manual_mode: false) + rolled_back = false + phantom_migrations.reverse_each do |migration| next unless status_up?(migration) + rolled_back = true show_info_for(migration) if manual_mode migrate(migration) if !manual_mode || user_wants_rollback? rescue StandardError => e handle_rollback_error(migration, e) end + + rolled_back end def phantom_migrations @@ -99,7 +104,11 @@ def handle_rollback_error(migration, exception) ERROR puts colorize(error_message, :red) - ActualDbSchema.failed << FailedMigration.new(migration: migration, exception: exception) + ActualDbSchema.failed << FailedMigration.new( + migration: migration, + exception: exception, + branch: branch_for(migration.version.to_s) + ) end end end diff --git a/test/rake_task_test.rb b/test/rake_task_test.rb index 736c3ff..5c86c42 100644 --- a/test/rake_task_test.rb +++ b/test/rake_task_test.rb @@ -87,6 +87,9 @@ def down utils.run_migrations assert_equal(%w[20130906111510_irreversible.rb], ActualDbSchema.failed.map { |m| File.basename(m.filename) }) assert_match(/1 phantom migration\(s\) could not be rolled back automatically/, TestingState.output) + assert_match(/Try these steps to fix and move forward:/, TestingState.output) + assert_match(/Below are the details of the problematic migrations:/, TestingState.output) + assert_match(%r{File: tmp/migrated/20130906111510_irreversible.rb}, TestingState.output) end end end