class RubbyCop::Cop::Rails::ReversibleMigration

This cop checks whether the change method of the migration file is reversible.

@example

# bad
def change
  change_table :users do |t|
    t.column :name, :string
  end
end

# good
def change
  create_table :users do |t|
    t.string :name
  end
end

# good
def change
  reversible do |dir|
    change_table :users do |t|
      dir.up do
        t.column :name, :string
      end

      dir.down do
        t.remove :name
      end
    end
  end
end

@example

# drop_table

# bad
def change
  drop_table :users
end

# good
def change
  drop_table :users do |t|
    t.string :name
  end
end

@example

# change_column_default

# bad
def change
  change_column_default(:suppliers, :qualification, 'new')
end

# good
def change
  change_column_default(:posts, :state, from: nil, to: "draft")
end

@example

# remove_column

# bad
def change
  remove_column(:suppliers, :qualification)
end

# good
def change
  remove_column(:suppliers, :qualification, :string)
end

@example

# remove_foreign_key

# bad
def change
  remove_foreign_key :accounts, column: :owner_id
end

# good
def change
  remove_foreign_key :accounts, :branches
end

@see api.rubyonrails.org/classes/ActiveRecord/Migration/CommandRecorder.html

Constants

MSG

Public Instance Methods

on_send(node) click to toggle source
# File lib/rubbycop/cop/rails/reversible_migration.rb, line 117
def on_send(node)
  return unless within_change_method?(node)
  return if within_reversible_block?(node)

  check_irreversible_schema_statement_node(node)
  check_drop_table_node(node)
  check_change_column_default_node(node)
  check_remove_column_node(node)
  check_remove_foreign_key_node(node)
end

Private Instance Methods

all_hash_key?(args, *keys) click to toggle source
# File lib/rubbycop/cop/rails/reversible_migration.rb, line 204
def all_hash_key?(args, *keys)
  return false unless args && args.hash_type?

  hash_keys = args.to_a.map do |arg|
    arg.to_a.first.children.first.to_sym
  end

  hash_keys & keys == keys
end
check_change_column_default_node(node) click to toggle source
# File lib/rubbycop/cop/rails/reversible_migration.rb, line 147
def check_change_column_default_node(node)
  change_column_default_call(node) do |args|
    unless all_hash_key?(args.first, :from, :to)
      add_offense(
        node, :expression,
        format(MSG, 'change_column_default(without :from and :to)')
      )
    end
  end
end
check_drop_table_node(node) click to toggle source
# File lib/rubbycop/cop/rails/reversible_migration.rb, line 136
def check_drop_table_node(node)
  drop_table_call(node) do
    unless node.parent.block_type?
      add_offense(
        node, :expression,
        format(MSG, 'drop_table(without block)')
      )
    end
  end
end
check_irreversible_schema_statement_node(node) click to toggle source
# File lib/rubbycop/cop/rails/reversible_migration.rb, line 130
def check_irreversible_schema_statement_node(node)
  irreversible_schema_statement_call(node) do |method_name|
    add_offense(node, :expression, format(MSG, method_name))
  end
end
check_remove_column_node(node) click to toggle source
# File lib/rubbycop/cop/rails/reversible_migration.rb, line 158
def check_remove_column_node(node)
  remove_column_call(node) do |args|
    if args.to_a.size < 3
      add_offense(
        node, :expression,
        format(MSG, 'remove_column(without type)')
      )
    end
  end
end
check_remove_foreign_key_node(node) click to toggle source
# File lib/rubbycop/cop/rails/reversible_migration.rb, line 169
def check_remove_foreign_key_node(node)
  remove_foreign_key_call(node) do |arg|
    if arg.hash_type?
      add_offense(
        node, :expression,
        format(MSG, 'remove_foreign_key(without table)')
      )
    end
  end
end
within_change_method?(node) click to toggle source
# File lib/rubbycop/cop/rails/reversible_migration.rb, line 180
def within_change_method?(node)
  parent = node.parent
  while parent
    if parent.def_type?
      method_name, = *parent
      return true if method_name == :change
    end
    parent = parent.parent
  end
  false
end
within_reversible_block?(node) click to toggle source
# File lib/rubbycop/cop/rails/reversible_migration.rb, line 192
def within_reversible_block?(node)
  parent = node.parent
  while parent
    if parent.block_type?
      _, block_name = *parent.to_a.first
      return true if block_name == :reversible
    end
    parent = parent.parent
  end
  false
end