gitlab-org--gitlab-foss/rubocop/cop/migration/create_table_with_foreign_k...

100 lines
3.0 KiB
Ruby

# frozen_string_literal: true
require_relative '../../migration_helpers'
module RuboCop
module Cop
module Migration
class CreateTableWithForeignKeys < RuboCop::Cop::Cop
include MigrationHelpers
MSG = 'Creating a table with more than one foreign key at once violates our migration style guide. ' \
'For more details check the https://docs.gitlab.com/ee/development/migration_style_guide.html#examples'
def_node_matcher :create_table_with_block?, <<~PATTERN
(block
(send nil? :create_table ...)
(args (arg _var))
_)
PATTERN
def_node_search :belongs_to_and_references, <<~PATTERN
(send _var {:references :belongs_to} $...)
PATTERN
def_node_search :foreign_key_options, <<~PATTERN
(_pair
{(sym :foreign_key) (str "foreign_key")}
{(hash _) (true)}
)
PATTERN
def_node_search :to_table, <<~PATTERN
(_pair
{(sym :to_table) (str "to_table")} {(sym $...) (str $...)}
)
PATTERN
def_node_matcher :argument_name?, <<~PATTERN
{(sym $...) (str $...)}
PATTERN
def_node_search :standalone_foreign_keys, <<~PATTERN
(send _var :foreign_key $...)
PATTERN
def on_send(node)
return unless in_migration?(node)
return unless node.command?(:create_table)
return unless create_table_with_block?(node.parent)
add_offense(node) if violates?(node.parent)
end
private
def violates?(node)
tables = all_target_tables(node).uniq
tables.length > 1 && !(tables & high_traffic_tables).empty?
end
def all_target_tables(node)
belongs_to_and_references_foreign_key_targets(node) + standalone_foreign_key_targets(node)
end
def belongs_to_and_references_foreign_key_targets(node)
belongs_to_and_references(node).select { |candidate| has_fk_option?(candidate) }
.flat_map { |definition| definition_to_table_names(definition) }
.compact
end
def standalone_foreign_key_targets(node)
standalone_foreign_keys(node).flat_map { |definition| definition_to_table_names(definition) }
.compact
end
def has_fk_option?(candidate)
foreign_key_options(candidate.last).first
end
def definition_to_table_names(definition)
table_name_from_options(definition.last) || arguments_to_table_names(definition)
end
def table_name_from_options(options)
to_table(options).to_a.first&.first
end
def arguments_to_table_names(arguments)
arguments.select { |argument| argument_name?(argument) }
.map(&:value)
.map(&:to_s)
.map(&:pluralize)
.map(&:to_sym)
end
end
end
end
end