mirror of
https://github.com/discourse/discourse.git
synced 2024-11-23 19:37:55 +08:00
6a3c8fe69c
Often we need to amend our schema, it is tempting to use drop_table, rename_column and drop_column to amned schema trouble though is that existing code that is running in production can depend on the existance of previous schema leading to application breaking until new code base is deployed. The commit enforces new rules to ensure we can never drop tables or columns in migrations and instead use Migration::ColumnDropper and Migration::TableDropper to defer drop the db objects
119 lines
3.5 KiB
Ruby
119 lines
3.5 KiB
Ruby
module Migration; end
|
|
|
|
class Discourse::InvalidMigration < StandardError; end
|
|
|
|
class Migration::SafeMigrate
|
|
module SafeMigration
|
|
UNSAFE_VERSION = 20180321015220
|
|
@@enable_safe = true
|
|
|
|
def self.enable_safe!
|
|
@@enable_safe = true
|
|
end
|
|
|
|
def self.disable_safe!
|
|
@@enable_safe = false
|
|
end
|
|
|
|
def migrate(direction)
|
|
if direction == :up && version && version > UNSAFE_VERSION && @@enable_safe != false
|
|
Migration::SafeMigrate.enable!
|
|
end
|
|
super
|
|
ensure
|
|
Migration::SafeMigrate.disable!
|
|
end
|
|
end
|
|
|
|
module NiceErrors
|
|
def migrate
|
|
super
|
|
rescue => e
|
|
if e.cause.is_a?(Discourse::InvalidMigration)
|
|
def e.cause; nil; end
|
|
def e.backtrace
|
|
super.reject do |frame|
|
|
frame =~ /safe_migrate\.rb/ || frame =~ /schema_migration_details\.rb/
|
|
end
|
|
end
|
|
raise e
|
|
else
|
|
raise e
|
|
end
|
|
end
|
|
end
|
|
|
|
def self.enable!
|
|
return if PG::Connection.method_defined?(:exec_migrator_unpatched)
|
|
|
|
PG::Connection.class_eval do
|
|
alias_method :exec_migrator_unpatched, :exec
|
|
alias_method :async_exec_migrator_unpatched, :async_exec
|
|
|
|
def exec(*args, &blk)
|
|
Migration::SafeMigrate.protect!(args[0])
|
|
exec_migrator_unpatched(*args, &blk)
|
|
end
|
|
|
|
def async_exec(*args, &blk)
|
|
Migration::SafeMigrate.protect!(args[0])
|
|
async_exec_migrator_unpatched(*args, &blk)
|
|
end
|
|
end
|
|
end
|
|
|
|
def self.disable!
|
|
return if !PG::Connection.method_defined?(:exec_migrator_unpatched)
|
|
PG::Connection.class_eval do
|
|
alias_method :exec, :exec_migrator_unpatched
|
|
alias_method :async_exec, :async_exec_migrator_unpatched
|
|
|
|
remove_method :exec_migrator_unpatched
|
|
remove_method :async_exec_migrator_unpatched
|
|
end
|
|
end
|
|
|
|
def self.patch_active_record!
|
|
ActiveSupport.on_load(:active_record) do
|
|
ActiveRecord::Migration.prepend(SafeMigration)
|
|
end
|
|
|
|
if defined?(ActiveRecord::Tasks::DatabaseTasks)
|
|
ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(NiceErrors)
|
|
end
|
|
end
|
|
|
|
def self.protect!(sql)
|
|
if sql =~ /^\s*drop\s+table/i
|
|
$stdout.puts("", <<~STR)
|
|
WARNING
|
|
-------------------------------------------------------------------------------------
|
|
An attempt was made to drop a table in a migration
|
|
SQL used was: '#{sql}'
|
|
Please use the deferred pattrn using Migration::TableDropper in db/seeds to drop
|
|
the table.
|
|
|
|
This protection is in place to protect us against dropping tables that are currently
|
|
in use by live applications.
|
|
STR
|
|
raise Discourse::InvalidMigration, "Attempt was made to drop a table"
|
|
elsif sql =~ /^\s*alter\s+table.*(rename|drop)/i
|
|
$stdout.puts("", <<~STR)
|
|
WARNING
|
|
-------------------------------------------------------------------------------------
|
|
An attempt was made to drop or rename a column in a migration
|
|
SQL used was: '#{sql}'
|
|
Please use the deferred pattrn using Migration::ColumnDropper in db/seeds to drop
|
|
or rename columns.
|
|
|
|
Note, to minimize disruption use self.ignored_columns = ["column name"] on your
|
|
ActiveRecord model, this can be removed 6 months or so later.
|
|
|
|
This protection is in place to protect us against dropping columns that are currently
|
|
in use by live applications.
|
|
STR
|
|
raise Discourse::InvalidMigration, "Attempt was made to rename or delete column"
|
|
end
|
|
end
|
|
end
|