class ActiveRecord::Migration::CommandRecorder

Migration Command Recorder

ActiveRecord::Migration::CommandRecorder records commands done during a migration and knows how to reverse those commands. The CommandRecorder knows how to invert the following commands:

Constants

ReversibleAndIrreversibleMethods

Attributes

commands[RW]
delegate[RW]
reverting[RW]

Public Class Methods

new(delegate = nil) click to toggle source
# File lib/active_record/migration/command_recorder.rb, line 67
def initialize(delegate = nil)
  @commands = []
  @delegate = delegate
  @reverting = false
end

Public Instance Methods

inverse_of(command, args, &block) click to toggle source

Returns the inverse of the given command. For example:

recorder.inverse_of(:rename_table, [:old, :new])
# => [:rename_table, [:new, :old]]

If the inverse of a command requires several commands, returns array of commands.

recorder.inverse_of(:remove_columns, [:some_table, :foo, :bar, type: :string])
# => [[:add_column, :some_table, :foo, :string], [:add_column, :some_table, :bar, :string]]

This method will raise an IrreversibleMigration exception if it cannot invert the command.

# File lib/active_record/migration/command_recorder.rb, line 114
      def inverse_of(command, args, &block)
        method = :"invert_#{command}"
        raise IrreversibleMigration, <<~MSG unless respond_to?(method, true)
          This migration uses #{command}, which is not automatically reversible.
          To make the migration reversible you can either:
          1. Define #up and #down methods in place of the #change method.
          2. Use the #reversible method to define reversible behavior.
        MSG
        send(method, args, &block)
      end
record(*command, &block) click to toggle source

Record command. command should be a method name and arguments. For example:

recorder.record(:method_name, [:arg1, :arg2])
# File lib/active_record/migration/command_recorder.rb, line 94
def record(*command, &block)
  if @reverting
    @commands << inverse_of(*command, &block)
  else
    @commands << (command << block)
  end
end
replay(migration) click to toggle source
# File lib/active_record/migration/command_recorder.rb, line 148
def replay(migration)
  commands.each do |cmd, args, block|
    migration.send(cmd, *args, &block)
  end
end
revert() { || ... } click to toggle source

While executing the given block, the recorded will be in reverting mode. All commands recorded will end up being recorded reverted and in reverse order. For example:

recorder.revert{ recorder.record(:rename_table, [:old, :new]) }
# same effect as recorder.record(:rename_table, [:new, :old])
# File lib/active_record/migration/command_recorder.rb, line 80
def revert
  @reverting = !@reverting
  previous = @commands
  @commands = []
  yield
ensure
  @commands = previous.concat(@commands.reverse)
  @reverting = !@reverting
end

Private Instance Methods

invert_add_check_constraint(args) click to toggle source
Calls superclass method
# File lib/active_record/migration/command_recorder.rb, line 324
def invert_add_check_constraint(args)
  if (options = args.last).is_a?(Hash)
    options.delete(:validate)
    options[:if_exists] = options.delete(:if_not_exists) if options.key?(:if_not_exists)
  end
  super
end
invert_add_foreign_key(args) click to toggle source
Calls superclass method
# File lib/active_record/migration/command_recorder.rb, line 285
def invert_add_foreign_key(args)
  args.last.delete(:validate) if args.last.is_a?(Hash)
  super
end
invert_add_unique_constraint(args) click to toggle source
Calls superclass method
# File lib/active_record/migration/command_recorder.rb, line 346
def invert_add_unique_constraint(args)
  options = args.dup.extract_options!

  raise ActiveRecord::IrreversibleMigration, "add_unique_constraint is not reversible if given an using_index." if options[:using_index]
  super
end
invert_change_column_comment(args) click to toggle source
# File lib/active_record/migration/command_recorder.rb, line 304
def invert_change_column_comment(args)
  table, column, options = args

  unless options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to)
    raise ActiveRecord::IrreversibleMigration, "change_column_comment is only reversible if given a :from and :to option."
  end

  [:change_column_comment, [table, column, from: options[:to], to: options[:from]]]
end
invert_change_column_default(args) click to toggle source
# File lib/active_record/migration/command_recorder.rb, line 270
def invert_change_column_default(args)
  table, column, options = args

  unless options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to)
    raise ActiveRecord::IrreversibleMigration, "change_column_default is only reversible if given a :from and :to option."
  end

  [:change_column_default, [table, column, from: options[:to], to: options[:from]]]
end
invert_change_column_null(args) click to toggle source
# File lib/active_record/migration/command_recorder.rb, line 280
def invert_change_column_null(args)
  args[2] = !args[2]
  [:change_column_null, args]
end
invert_change_table_comment(args) click to toggle source
# File lib/active_record/migration/command_recorder.rb, line 314
def invert_change_table_comment(args)
  table, options = args

  unless options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to)
    raise ActiveRecord::IrreversibleMigration, "change_table_comment is only reversible if given a :from and :to option."
  end

  [:change_table_comment, [table, from: options[:to], to: options[:from]]]
end
invert_create_table(args, &block) click to toggle source
Calls superclass method
# File lib/active_record/migration/command_recorder.rb, line 197
def invert_create_table(args, &block)
  if args.last.is_a?(Hash)
    args.last.delete(:if_not_exists)
  end
  super
end
invert_drop_enum(args) click to toggle source
Calls superclass method
# File lib/active_record/migration/command_recorder.rb, line 360
def invert_drop_enum(args)
  _enum, values = args.dup.tap(&:extract_options!)
  raise ActiveRecord::IrreversibleMigration, "drop_enum is only reversible if given a list of enum values." unless values
  super
end
invert_drop_table(args, &block) click to toggle source
Calls superclass method
# File lib/active_record/migration/command_recorder.rb, line 204
def invert_drop_table(args, &block)
  options = args.extract_options!
  options.delete(:if_exists)

  if args.size > 1
    raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given a single table name."
  end

  if args.size == 1 && options == {} && block == nil
    raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)."
  end

  super(args.push(options), &block)
end
invert_drop_virtual_table(args) click to toggle source
Calls superclass method
# File lib/active_record/migration/command_recorder.rb, line 386
def invert_drop_virtual_table(args)
  _enum, values = args.dup.tap(&:extract_options!)
  raise ActiveRecord::IrreversibleMigration, "drop_virtual_table is only reversible if given options." unless values
  super
end
invert_remove_check_constraint(args) click to toggle source
Calls superclass method
# File lib/active_record/migration/command_recorder.rb, line 332
def invert_remove_check_constraint(args)
  raise ActiveRecord::IrreversibleMigration, "remove_check_constraint is only reversible if given an expression." if args.size < 2

  if (options = args.last).is_a?(Hash)
    options[:if_not_exists] = options.delete(:if_exists) if options.key?(:if_exists)
  end
  super
end
invert_remove_column(args) click to toggle source
Calls superclass method
# File lib/active_record/migration/command_recorder.rb, line 226
def invert_remove_column(args)
  raise ActiveRecord::IrreversibleMigration, "remove_column is only reversible if given a type." if args.size <= 2
  super
end
invert_remove_columns(args) click to toggle source
# File lib/active_record/migration/command_recorder.rb, line 231
def invert_remove_columns(args)
  unless args[-1].is_a?(Hash) && args[-1].has_key?(:type)
    raise ActiveRecord::IrreversibleMigration, "remove_columns is only reversible if given a type."
  end

  [:add_columns, args]
end
invert_remove_exclusion_constraint(args) click to toggle source
Calls superclass method
# File lib/active_record/migration/command_recorder.rb, line 341
def invert_remove_exclusion_constraint(args)
  raise ActiveRecord::IrreversibleMigration, "remove_exclusion_constraint is only reversible if given an expression." if args.size < 2
  super
end
invert_remove_foreign_key(args) click to toggle source
# File lib/active_record/migration/command_recorder.rb, line 290
def invert_remove_foreign_key(args)
  options = args.extract_options!
  from_table, to_table = args

  to_table ||= options.delete(:to_table)

  raise ActiveRecord::IrreversibleMigration, "remove_foreign_key is only reversible if given a second table" if to_table.nil?

  reversed_args = [from_table, to_table]
  reversed_args << options unless options.empty?

  [:add_foreign_key, reversed_args]
end
invert_remove_index(args) click to toggle source
# File lib/active_record/migration/command_recorder.rb, line 249
def invert_remove_index(args)
  options = args.extract_options!
  table, columns = args

  columns ||= options.delete(:column)

  unless columns
    raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option."
  end

  options.delete(:if_exists)

  args = [table, columns]
  args << options unless options.empty?

  [:add_index, args]
end
invert_remove_unique_constraint(args) click to toggle source
Calls superclass method
# File lib/active_record/migration/command_recorder.rb, line 353
def invert_remove_unique_constraint(args)
  _table, columns = args.dup.tap(&:extract_options!)

  raise ActiveRecord::IrreversibleMigration, "remove_unique_constraint is only reversible if given an column_name." if columns.blank?
  super
end
invert_rename_column(args) click to toggle source
# File lib/active_record/migration/command_recorder.rb, line 244
def invert_rename_column(args)
  table_name, old_name, new_name = args
  [:rename_column, [table_name, new_name, old_name]]
end
invert_rename_enum(args) click to toggle source
# File lib/active_record/migration/command_recorder.rb, line 366
def invert_rename_enum(args)
  name, new_name, = args

  if new_name.is_a?(Hash) && new_name.key?(:to)
    new_name = new_name[:to]
  end

  [:rename_enum, [new_name, name]]
end
invert_rename_enum_value(args) click to toggle source
# File lib/active_record/migration/command_recorder.rb, line 376
def invert_rename_enum_value(args)
  type_name, options = args

  unless options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to)
    raise ActiveRecord::IrreversibleMigration, "rename_enum_value is only reversible if given a :from and :to option."
  end

  [:rename_enum_value, [type_name, from: options[:to], to: options[:from]]]
end
invert_rename_index(args) click to toggle source
# File lib/active_record/migration/command_recorder.rb, line 239
def invert_rename_index(args)
  table_name, old_name, new_name = args
  [:rename_index, [table_name, new_name, old_name]]
end
invert_rename_table(args) click to toggle source
# File lib/active_record/migration/command_recorder.rb, line 219
def invert_rename_table(args)
  old_name, new_name, options = args
  args = [new_name, old_name]
  args << options if options
  [:rename_table, args]
end
invert_transaction(args, &block) click to toggle source
# File lib/active_record/migration/command_recorder.rb, line 186
def invert_transaction(args, &block)
  sub_recorder = CommandRecorder.new(delegate)
  sub_recorder.revert(&block)

  invertions_proc = proc {
    sub_recorder.replay(self)
  }

  [:transaction, args, invertions_proc]
end
method_missing(method, ...) click to toggle source

Forwards any missing method call to the target.

Calls superclass method
# File lib/active_record/migration/command_recorder.rb, line 397
def method_missing(method, ...)
  if delegate.respond_to?(method)
    delegate.public_send(method, ...)
  else
    super
  end
end
respond_to_missing?(method, _) click to toggle source
Calls superclass method
# File lib/active_record/migration/command_recorder.rb, line 392
def respond_to_missing?(method, _)
  super || delegate.respond_to?(method)
end