spec/linters/migration/add_constraint_name.rb
module RuboCop
module Cop
module Migration
class AddConstraintName < RuboCop::Cop::Cop
# Postgres and MySQL have different naming conventions, so if we need to remove them we cannot predict accurately what the constraint name would be.
MSG = 'Please explicitly name your index or constraint.'.freeze
CONSTRAINT_METHODS = %i[
add_unique_constraint add_constraint add_foreign_key add_index add_primary_key add_full_text_index add_spatial_index
unique_constraint constraint foreign_key index primary_key full_text_index spatial_index
].freeze
COLUMN_ADDING_METHODS = %i[
add_column column String Integer
].freeze
def on_block(node)
node.each_descendant(:send) do |send_node|
method = method_name(send_node)
next unless constraint_adding_method?(method) || column_adding_method?(method)
opts = send_node.children.last
missing_named_constraint = true
if opts
if constraint_adding_method?(method)
missing_named_constraint = add_constraint_missing_name?(opts)
elsif column_adding_method?(method)
missing_named_constraint = add_column_missing_name?(opts)
end
end
add_offense(send_node, location: :expression) if missing_named_constraint
end
end
private
def constraint_adding_method?(method)
CONSTRAINT_METHODS.include?(method)
end
def column_adding_method?(method)
COLUMN_ADDING_METHODS.include?(method)
end
def add_constraint_missing_name?(opts)
return true unless opts.type == :hash
opts.each_node(:pair) do |pair|
return false if hash_key_type(pair) == :sym && hash_key_name(pair) == :name
end
true
end
def add_column_missing_name?(opts)
return true if opts.type == :sym && %i[index primary_key unique].include?(sym_opts_name(opts))
needs_named_index = false
needs_named_primary_key = false
needs_named_unique_constraint = false
opts.each_node(:pair) do |pair|
next unless hash_key_type(pair) == :sym
case hash_key_name(pair)
when :index then needs_named_index = true
when :primary_key then needs_named_primary_key = true
when :unique then needs_named_unique_constraint = true
end
end
opts.each_node(:pair) do |pair|
next unless hash_key_type(pair) == :sym
case hash_key_name(pair)
when :name then needs_named_index = false
when :primary_key_constraint_name then needs_named_primary_key = false
when :unique_constraint_name then needs_named_unique_constraint = false
end
end
[needs_named_index, needs_named_primary_key, needs_named_unique_constraint].any?
end
def method_name(node)
node.children[1]
end
def hash_key_type(pair)
pair.children[0].type
end
def hash_key_name(pair)
pair.children[0].children[0]
end
def sym_opts_name(opts)
opts.children[0]
end
end
end
end
end