diff --git a/db/migrate/20160317092222_add_moved_to_to_issue.rb b/db/migrate/20160317092222_add_moved_to_to_issue.rb index 461e7fb3a9b..2bf549d7ecd 100644 --- a/db/migrate/20160317092222_add_moved_to_to_issue.rb +++ b/db/migrate/20160317092222_add_moved_to_to_issue.rb @@ -1,5 +1,5 @@ class AddMovedToToIssue < ActiveRecord::Migration def change - add_reference :issues, :moved_to, references: :issues + add_reference :issues, :moved_to, references: :issues # rubocop:disable Migration/AddReference end end diff --git a/rubocop/cop/migration/add_reference.rb b/rubocop/cop/migration/add_reference.rb new file mode 100644 index 00000000000..4b67270c97a --- /dev/null +++ b/rubocop/cop/migration/add_reference.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true +require_relative '../../migration_helpers' + +module RuboCop + module Cop + module Migration + # Cop that checks if a foreign key constraint is added and require a index for it + class AddReference < RuboCop::Cop::Cop + include MigrationHelpers + + MSG = '`add_reference` requires `index: true`' + + def on_send(node) + return unless in_migration?(node) + + name = node.children[1] + + return unless name == :add_reference + + opts = node.children.last + + add_offense(node, location: :selector) unless opts && opts.type == :hash + + index_present = false + + opts.each_node(:pair) do |pair| + index_present ||= index_enabled?(pair) + end + + add_offense(node, location: :selector) unless index_present + end + + private + + def index_enabled?(pair) + hash_key_type(pair) == :sym && hash_key_name(pair) == :index && pair.children[1].true_type? + end + + def hash_key_type(pair) + pair.children[0].type + end + + def hash_key_name(pair) + pair.children[0].children[0] + end + end + end + end +end diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb index aa7ae601f75..a427208cdab 100644 --- a/rubocop/rubocop.rb +++ b/rubocop/rubocop.rb @@ -11,6 +11,7 @@ require_relative 'cop/migration/add_column' require_relative 'cop/migration/add_concurrent_foreign_key' require_relative 'cop/migration/add_concurrent_index' require_relative 'cop/migration/add_index' +require_relative 'cop/migration/add_reference' require_relative 'cop/migration/add_timestamps' require_relative 'cop/migration/datetime' require_relative 'cop/migration/hash_index' diff --git a/spec/rubocop/cop/migration/add_reference_spec.rb b/spec/rubocop/cop/migration/add_reference_spec.rb new file mode 100644 index 00000000000..8f795bb561e --- /dev/null +++ b/spec/rubocop/cop/migration/add_reference_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +require 'rubocop' +require 'rubocop/rspec/support' + +require_relative '../../../../rubocop/cop/migration/add_reference' + +describe RuboCop::Cop::Migration::AddReference do + include CopHelper + + let(:cop) { described_class.new } + + context 'outside of a migration' do + it 'does not register any offenses' do + expect_no_offenses(<<~RUBY) + def up + add_reference(:projects, :users) + end + RUBY + end + end + + context 'in a migration' do + before do + allow(cop).to receive(:in_migration?).and_return(true) + end + + it 'registers an offense when using add_reference without index' do + expect_offense(<<~RUBY) + call do + add_reference(:projects, :users) + ^^^^^^^^^^^^^ `add_reference` requires `index: true` + end + RUBY + end + + it 'registers an offense when using add_reference index disabled' do + expect_offense(<<~RUBY) + def up + add_reference(:projects, :users, index: false) + ^^^^^^^^^^^^^ `add_reference` requires `index: true` + end + RUBY + end + + it 'does not register an offense when using add_reference with index enabled' do + expect_no_offenses(<<~RUBY) + def up + add_reference(:projects, :users, index: true) + end + RUBY + end + end +end