Class: RuboCop::Cop::DevDoc::Migration::AvoidConditionalSchemaChanges

Inherits:
Base
  • Object
show all
Defined in:
lib/rubocop/cop/dev_doc/migration/avoid_conditional_schema_changes.rb

Overview

Flag conditional schema-change helpers (‘add_column_if_not_exists`, `column_exists?`, etc.) inside migration files.

## Rationale Migrations are deterministic state transitions: “DB was at state N; after this migration it is at state N+1.” Conditional schema helpers imply “I don’t know what state the DB is in,” which contradicts the migration model and hides schema drift.

If a column “might already exist,” that is a symptom — investigate why before papering over it with a defensive guard.

The escape hatch is a per-line ‘rubocop:disable` comment with a rationale explaining the known-drift repair.

❌ hides drift; state is unknown
add_column_if_not_exists :users, :something, :string
add_column :users, :bar, :string unless column_exists?(:users, :bar)

✔️ declarative state transition
add_column :users, :something, :string

✔️ documented one-shot drift repair (escape hatch)
# rubocop:disable DevDoc/Migration/AvoidConditionalSchemaChanges
add_column_if_not_exists :users, :something, :string
# rubocop:enable DevDoc/Migration/AvoidConditionalSchemaChanges

Examples:

# bad
add_column_if_not_exists :users, :something, :string

# bad
add_index_if_not_exists :users, :email

# bad
remove_column_if_exists :users, :legacy

# bad (predicate guard shape)
add_column :users, :foo, :string unless column_exists?(:users, :foo)

# good
add_column :users, :something, :string

Constant Summary collapse

IF_NOT_EXISTS_METHODS =
%i[
  add_column_if_not_exists
  add_index_if_not_exists
  add_foreign_key_if_not_exists
  add_reference_if_not_exists
  remove_column_if_exists
  remove_index_if_exists
  remove_foreign_key_if_exists
  remove_reference_if_exists
].freeze
EXISTENCE_PREDICATES =
%i[
  column_exists?
  table_exists?
  index_exists?
  foreign_key_exists?
].freeze
MSG_IF_NOT_EXISTS =
'`%<method>s` hides schema drift. Use the non-conditional form and investigate ' \
'why states diverge. Suppress with a `rubocop:disable` comment only for documented ' \
'one-shot drift repairs.'.freeze
MSG_PREDICATE =
'`%<method>s` guard hides schema drift. Use unconditional schema operations and ' \
'investigate why states diverge. Suppress with a `rubocop:disable` comment only for ' \
'documented one-shot drift repairs.'.freeze

Instance Method Summary collapse

Instance Method Details

#on_send(node) ⇒ Object



76
77
78
79
80
81
82
83
84
# File 'lib/rubocop/cop/dev_doc/migration/avoid_conditional_schema_changes.rb', line 76

def on_send(node)
  if IF_NOT_EXISTS_METHODS.include?(node.method_name)
    add_offense(node.loc.selector,
                message: format(MSG_IF_NOT_EXISTS, method: node.method_name))
  elsif EXISTENCE_PREDICATES.include?(node.method_name)
    add_offense(node.loc.selector,
                message: format(MSG_PREDICATE, method: node.method_name))
  end
end