class ActiveRecord::Migration::CommandRecorder

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 activerecord/lib/active_record/migration/command_recorder.rb, line 44
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]]

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

# File activerecord/lib/active_record/migration/command_recorder.rb, line 86
      def inverse_of(command, args, &block)
        method = :"invert_#{command}"
        raise IrreversibleMigration, <<-MSG.strip_heredoc 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 activerecord/lib/active_record/migration/command_recorder.rb, line 71
def record(*command, &block)
  if @reverting
    @commands << inverse_of(*command, &block)
  else
    @commands << (command << 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 activerecord/lib/active_record/migration/command_recorder.rb, line 57
def revert
  @reverting = !@reverting
  previous = @commands
  @commands = []
  yield
ensure
  @commands = previous.concat(@commands.reverse)
  @reverting = !@reverting
end

Private Instance Methods

invert_add_foreign_key(args) click to toggle source
# File activerecord/lib/active_record/migration/command_recorder.rb, line 201
def invert_add_foreign_key(args)
  from_table, to_table, add_options = args
  add_options ||= {}

  if add_options[:name]
    options = { name: add_options[:name] }
  elsif add_options[:column]
    options = { column: add_options[:column] }
  else
    options = to_table
  end

  [:remove_foreign_key, [from_table, options]]
end
invert_add_index(args) click to toggle source
# File activerecord/lib/active_record/migration/command_recorder.rb, line 160
def invert_add_index(args)
  table, columns, options = *args
  options ||= {}

  index_name = options[:name]
  options_hash = index_name ? { name: index_name } : { column: columns }

  [:remove_index, [table, options_hash]]
end
invert_change_column_default(args) click to toggle source
# File activerecord/lib/active_record/migration/command_recorder.rb, line 186
def invert_change_column_default(args)
  table, column, options = *args

  unless options && 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 activerecord/lib/active_record/migration/command_recorder.rb, line 196
def invert_change_column_null(args)
  args[2] = !args[2]
  [:change_column_null, args]
end
invert_drop_table(args, &block) click to toggle source
Calls superclass method
# File activerecord/lib/active_record/migration/command_recorder.rb, line 136
def invert_drop_table(args, &block)
  if args.size == 1 && block == nil
    raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)."
  end
  super
end
invert_remove_column(args) click to toggle source
Calls superclass method
# File activerecord/lib/active_record/migration/command_recorder.rb, line 147
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_foreign_key(args) click to toggle source
# File activerecord/lib/active_record/migration/command_recorder.rb, line 216
def invert_remove_foreign_key(args)
  from_table, to_table, remove_options = args
  raise ActiveRecord::IrreversibleMigration, "remove_foreign_key is only reversible if given a second table" if to_table.nil? || to_table.is_a?(Hash)

  reversed_args = [from_table, to_table]
  reversed_args << remove_options if remove_options

  [:add_foreign_key, reversed_args]
end
invert_remove_index(args) click to toggle source
# File activerecord/lib/active_record/migration/command_recorder.rb, line 170
def invert_remove_index(args)
  table, options_or_column = *args
  if (options = options_or_column).is_a?(Hash)
    unless options[:column]
      raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option."
    end
    options = options.dup
    [:add_index, [table, options.delete(:column), options]]
  elsif (column = options_or_column).present?
    [:add_index, [table, column]]
  end
end
invert_rename_column(args) click to toggle source
# File activerecord/lib/active_record/migration/command_recorder.rb, line 156
def invert_rename_column(args)
  [:rename_column, [args.first] + args.last(2).reverse]
end
invert_rename_index(args) click to toggle source
# File activerecord/lib/active_record/migration/command_recorder.rb, line 152
def invert_rename_index(args)
  [:rename_index, [args.first] + args.last(2).reverse]
end
invert_rename_table(args) click to toggle source
# File activerecord/lib/active_record/migration/command_recorder.rb, line 143
def invert_rename_table(args)
  [:rename_table, args.reverse]
end
method_missing(method, *args, &block) click to toggle source

Forwards any missing method call to the target.

Calls superclass method
# File activerecord/lib/active_record/migration/command_recorder.rb, line 231
def method_missing(method, *args, &block)
  if delegate.respond_to?(method)
    delegate.public_send(method, *args, &block)
  else
    super
  end
end
respond_to_missing?(method, _) click to toggle source
Calls superclass method
# File activerecord/lib/active_record/migration/command_recorder.rb, line 226
def respond_to_missing?(method, _)
  super || delegate.respond_to?(method)
end