Welcome to mirror list, hosted at ThFree Co, Russian Federation.

create_table_with_foreign_keys.rb « migration « cop « rubocop - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: e3039ac76a911b6fb56a97f893204bd988d88f53 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# frozen_string_literal: true

require_relative '../../migration_helpers'

module RuboCop
  module Cop
    module Migration
      class CreateTableWithForeignKeys < RuboCop::Cop::Base
        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