Skip to content

Commit

Permalink
Phantom migrations status rake task (#35)
Browse files Browse the repository at this point in the history
* phantom migrations rake task

* refactor

* refactor

* fix loading commands

* fix tests

* add tests
  • Loading branch information
ka8725 authored Jan 3, 2024
1 parent 706e6fd commit 8b1ba16
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 47 deletions.
4 changes: 4 additions & 0 deletions lib/actual_db_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
require_relative "actual_db_schema/patches/migrator"
require_relative "actual_db_schema/patches/migration_context"

require_relative "actual_db_schema/commands/base"
require_relative "actual_db_schema/commands/rollback"
require_relative "actual_db_schema/commands/list"

# The main module definition
module ActualDbSchema
raise NotImplementedError, "ActualDbSchema is only supported in Rails" unless defined?(Rails)
Expand Down
33 changes: 33 additions & 0 deletions lib/actual_db_schema/commands/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

module ActualDbSchema
module Commands
# Base class for all commands
class Base
def call
unless ActualDbSchema.config.fetch(:enabled, true)
raise "ActualDbSchema is disabled. Set ActualDbSchema.config[:enabled] = true to enable it."
end

if ActiveRecord::Migration.current_version >= 6
ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:rollback_branches")
end

call_impl
end

private

def call_impl
raise NotImplementedError
end

def context
@context ||=
ActiveRecord::Base.connection.migration_context.tap do |c|
c.extend(ActualDbSchema::Patches::MigrationContext)
end
end
end
end
end
37 changes: 37 additions & 0 deletions lib/actual_db_schema/commands/list.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

module ActualDbSchema
module Commands
# Shows the list of phantom migrations
class List < Base
private

def call_impl
preambule
table
end

def indexed_phantom_migrations
@indexed_phantom_migrations ||= context.migrations.index_by { |m| m.version.to_s }
end

def preambule
puts "\nPhantom migrations\n\n"
puts "Below is a list of irrelevant migrations executed in unmerged branches."
puts "To bring your database schema up to date, the migrations marked as \"up\" should be rolled back."
puts "\ndatabase: #{ActiveRecord::Base.connection_db_config.database}\n\n"
puts %(#{"Status".center(8)} #{"Migration ID".ljust(14)} Migration File)
puts "-" * 50
end

def table
context.migrations_status.each do |status, version|
migration = indexed_phantom_migrations[version]
next unless migration

puts %(#{status.center(8)} #{version.to_s.ljust(14)} #{migration.filename.gsub("#{Rails.root}/", "")})
end
end
end
end
end
22 changes: 22 additions & 0 deletions lib/actual_db_schema/commands/rollback.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

module ActualDbSchema
module Commands
# Rolls back all phantom migrations
class Rollback < Base
private

def call_impl
context.rollback_branches

return if ActualDbSchema.failed.empty?

puts ""
puts "[ActualDbSchema] Irreversible migrations were found from other branches. Roll them back or fix manually:"
puts ""
puts ActualDbSchema.failed.map { |migration| "- #{migration.filename}" }.join("\n")
puts ""
end
end
end
end
22 changes: 5 additions & 17 deletions lib/tasks/db.rake
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,12 @@
namespace :db do
desc "Rollback migrations that were run inside not a merged branch."
task rollback_branches: :load_config do
unless ActualDbSchema.config.fetch(:enabled, true)
raise "ActualDbSchema is disabled. Set ActualDbSchema.config[:enabled] = true to enable it."
end

if ActiveRecord::Migration.current_version >= 6
ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:rollback_branches")
end
ActualDbSchema::Commands::Rollback.new.call
end

context = ActiveRecord::Base.connection.migration_context
context.extend(ActualDbSchema::Patches::MigrationContext)
context.rollback_branches
if ActualDbSchema.failed.any?
puts ""
puts "[ActualDbSchema] Irreversible migrations were found from other branches. Roll them back or fix manually:"
puts ""
puts ActualDbSchema.failed.map { |migration| "- #{migration.filename}" }.join("\n")
puts ""
end
desc "List all phantom migrations - non-relevant migrations that were run inside not a merged branch."
task phantom_migrations: :load_config do
ActualDbSchema::Commands::List.new.call
end

task _dump: :rollback_branches
Expand Down
2 changes: 1 addition & 1 deletion test/dummy_app/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[6.0].define(version: 0) do
ActiveRecord::Schema[7.1].define(version: 0) do
end
65 changes: 36 additions & 29 deletions test/rake_task_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,6 @@

require "test_helper"

class TestingState
class << self
attr_accessor :up, :down
end

def self.reset
self.up = []
self.down = []
end

reset
end

def app_file(path)
Rails.application.config.root.join(path)
end
Expand Down Expand Up @@ -77,24 +64,26 @@ def prepare_phantom_migrations
delete_migrations_files # simulate switching branches
end

describe "db:rollback_branches" do
let(:migrated_files) do
Dir.glob(app_file("tmp/migrated/*.rb")).map { |f| File.basename(f) }.sort
def cleanup
delete_migrations_files
if ActiveRecord::SchemaMigration.respond_to?(:create_table)
ActiveRecord::SchemaMigration.create_table
else
ActiveRecord::SchemaMigration.new(ActiveRecord::Base.connection).create_table
end
run_sql("delete from schema_migrations")
remove_app_dir("tmp/migrated")
define_migrations
Rails.application.load_tasks
TestingState.reset
end

before do
delete_migrations_files
if ActiveRecord::SchemaMigration.respond_to?(:create_table)
ActiveRecord::SchemaMigration.create_table
else
ActiveRecord::SchemaMigration.new(ActiveRecord::Base.connection).create_table
end
run_sql("delete from schema_migrations")
remove_app_dir("tmp/migrated")
define_migrations
Rails.application.load_tasks
TestingState.reset
end
def migrated_files
Dir.glob(app_file("tmp/migrated/*.rb")).map { |f| File.basename(f) }.sort
end

describe "db:rollback_branches" do
before { cleanup }

it "creates the tmp/migrated folder" do
refute File.exist?(app_file("tmp/migrated"))
Expand Down Expand Up @@ -144,3 +133,21 @@ def down
end
end
end

describe "db:phantom_migrations" do
before { cleanup }

def run_task
Rake::Task["db:phantom_migrations"].invoke
Rake::Task["db:phantom_migrations"].reenable
end

it "shows the list of phantom migrations" do
prepare_phantom_migrations
run_task
assert_match(/ Status Migration ID Migration File/, TestingState.output)
assert_match(/--------------------------------------------------/, TestingState.output)
assert_match(%r{ up 20130906111511 tmp/migrated/20130906111511_first.rb}, TestingState.output)
assert_match(%r{ up 20130906111512 tmp/migrated/20130906111512_second.rb}, TestingState.output)
end
end
23 changes: 23 additions & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,26 @@ def initialize
ActiveRecord::Base.establish_connection(**db_config)

ActualDbSchema.config[:enabled] = true

class TestingState
class << self
attr_accessor :up, :down, :output
end

def self.reset
self.up = []
self.down = []
self.output = +""
end

reset
end

module Kernel
alias original_puts puts

def puts(*args)
TestingState.output << args.join("\n")
original_puts(*args)
end
end

0 comments on commit 8b1ba16

Please sign in to comment.