Commit 14ef40d4 authored by Patrick Bair's avatar Patrick Bair Committed by Mayra Cabrera

Require name for non-concurrent complex indexes

Improve upon an existing cop that requires an explicit name be provided
for indexes that are "complex", i.e. created with a hash of options
configuring their definition. The cop will no raise offenses at
non-concurrent index creation either via create_table or add_index.
parent 06a3f976
...@@ -10,8 +10,12 @@ module RuboCop ...@@ -10,8 +10,12 @@ module RuboCop
MSG = 'indexes added with custom options must be explicitly named' MSG = 'indexes added with custom options must be explicitly named'
def_node_matcher :match_create_table_index_with_options, <<~PATTERN
(send _ {:index } _ (hash $...))
PATTERN
def_node_matcher :match_add_index_with_options, <<~PATTERN def_node_matcher :match_add_index_with_options, <<~PATTERN
(send _ {:add_concurrent_index} _ _ (hash $...)) (send _ {:add_index :add_concurrent_index} _ _ (hash $...))
PATTERN PATTERN
def_node_matcher :name_option?, <<~PATTERN def_node_matcher :name_option?, <<~PATTERN
...@@ -26,7 +30,7 @@ module RuboCop ...@@ -26,7 +30,7 @@ module RuboCop
return unless in_migration?(node) return unless in_migration?(node)
node.each_descendant(:send) do |send_node| node.each_descendant(:send) do |send_node|
next unless add_index_offense?(send_node) next unless create_table_with_index_offense?(send_node) || add_index_offense?(send_node)
add_offense(send_node, location: :selector) add_offense(send_node, location: :selector)
end end
...@@ -34,6 +38,10 @@ module RuboCop ...@@ -34,6 +38,10 @@ module RuboCop
private private
def create_table_with_index_offense?(send_node)
match_create_table_index_with_options(send_node) { |option_nodes| needs_name_option?(option_nodes) }
end
def add_index_offense?(send_node) def add_index_offense?(send_node)
match_add_index_with_options(send_node) { |option_nodes| needs_name_option?(option_nodes) } match_add_index_with_options(send_node) { |option_nodes| needs_name_option?(option_nodes) }
end end
......
...@@ -14,32 +14,83 @@ RSpec.describe RuboCop::Cop::Migration::ComplexIndexesRequireName, type: :ruboco ...@@ -14,32 +14,83 @@ RSpec.describe RuboCop::Cop::Migration::ComplexIndexesRequireName, type: :ruboco
allow(cop).to receive(:in_migration?).and_return(true) allow(cop).to receive(:in_migration?).and_return(true)
end end
context 'when creating complex indexes as part of create_table' do
context 'when indexes are configured with an options hash, but no name' do context 'when indexes are configured with an options hash, but no name' do
it 'registers an offense' do it 'registers an offense' do
expect_offense(<<~RUBY) expect_offense(<<~RUBY)
class TestComplexIndexes < ActiveRecord::Migration[6.0] class TestComplexIndexes < ActiveRecord::Migration[6.0]
DOWNTIME = false DOWNTIME = false
INDEX_NAME = 'my_test_name' def up
create_table :test_table do |t|
t.integer :column1, null: false
t.integer :column2, null: false
t.jsonb :column3
t.index :column1, unique: true
t.index :column2, where: 'column1 = 0'
^^^^^ #{described_class::MSG}
t.index :column3, using: :gin
^^^^^ #{described_class::MSG}
end
end
disable_ddl_transaction! def down
drop_table :test_table
end
end
RUBY
def up expect(cop.offenses.map(&:cop_name)).to all(eq("Migration/#{described_class.name.demodulize}"))
add_concurrent_index :test_indexes, :column1 end
end
add_concurrent_index :test_indexes, :column2, where: "column2 = 'value'", order: { column4: :desc } context 'when indexes are configured with an options hash and name' do
^^^^^^^^^^^^^^^^^^^^ #{described_class::MSG} it 'registers no offense' do
expect_no_offenses(<<~RUBY)
class TestComplexIndexes < ActiveRecord::Migration[6.0]
DOWNTIME = false
add_concurrent_index :test_indexes, :column3, where: 'column3 = 10', name: 'idx_equal_to_10' def up
create_table :test_table do |t|
t.integer :column1, null: false
t.integer :column2, null: false
t.jsonb :column3
t.index :column1, unique: true
t.index :column2, where: 'column1 = 0', name: 'my_index_1'
t.index :column3, using: :gin, name: 'my_gin_index'
end
end end
def down def down
add_concurrent_index :test_indexes, :column4, 'unique' => true drop_table :test_table
end
end
RUBY
end
end
end
add_concurrent_index :test_indexes, :column4, 'unique' => true, where: 'column4 IS NOT NULL' context 'when indexes are added to an existing table' do
^^^^^^^^^^^^^^^^^^^^ #{described_class::MSG} context 'when indexes are configured with an options hash, but no name' do
it 'registers an offense' do
expect_offense(<<~RUBY)
class TestComplexIndexes < ActiveRecord::Migration[6.0]
DOWNTIME = false
disable_ddl_transaction!
add_concurrent_index :test_indexes, :column5, using: :gin, name: INDEX_NAME def up
add_index :test_indexes, :column1
add_index :test_indexes, :column2, where: "column2 = 'value'", order: { column4: :desc }
^^^^^^^^^ #{described_class::MSG}
end
def down
add_index :test_indexes, :column4, 'unique' => true, where: 'column4 IS NOT NULL'
^^^^^^^^^ #{described_class::MSG}
add_concurrent_index :test_indexes, :column6, using: :gin, opclass: :gin_trgm_ops add_concurrent_index :test_indexes, :column6, using: :gin, opclass: :gin_trgm_ops
^^^^^^^^^^^^^^^^^^^^ #{described_class::MSG} ^^^^^^^^^^^^^^^^^^^^ #{described_class::MSG}
...@@ -50,6 +101,35 @@ RSpec.describe RuboCop::Cop::Migration::ComplexIndexesRequireName, type: :ruboco ...@@ -50,6 +101,35 @@ RSpec.describe RuboCop::Cop::Migration::ComplexIndexesRequireName, type: :ruboco
expect(cop.offenses.map(&:cop_name)).to all(eq("Migration/#{described_class.name.demodulize}")) expect(cop.offenses.map(&:cop_name)).to all(eq("Migration/#{described_class.name.demodulize}"))
end end
end end
context 'when indexes are configured with an options hash and a name' do
it 'registers no offenses' do
expect_no_offenses(<<~RUBY)
class TestComplexIndexes < ActiveRecord::Migration[6.0]
DOWNTIME = false
INDEX_NAME = 'my_test_name'
disable_ddl_transaction!
def up
add_index :test_indexes, :column1
add_index :test_indexes, :column2, where: "column2 = 'value'", order: { column4: :desc }, name: 'my_index_1'
add_concurrent_index :test_indexes, :column3, where: 'column3 = 10', name: 'idx_equal_to_10'
end
def down
add_index :test_indexes, :column4, 'unique' => true, where: 'column4 IS NOT NULL', name: 'my_index_2'
add_concurrent_index :test_indexes, :column6, using: :gin, opclass: :gin_trgm_ops, name: INDEX_NAME
end
end
RUBY
end
end
end
end end
context 'outside migration' do context 'outside migration' do
...@@ -65,7 +145,13 @@ RSpec.describe RuboCop::Cop::Migration::ComplexIndexesRequireName, type: :ruboco ...@@ -65,7 +145,13 @@ RSpec.describe RuboCop::Cop::Migration::ComplexIndexesRequireName, type: :ruboco
disable_ddl_transaction! disable_ddl_transaction!
def up def up
add_concurrent_index :test_indexes, :column1, where: "some_column = 'value'" create_table :test_table do |t|
t.integer :column1
t.index :column1, where: 'column2 IS NOT NULL'
end
add_index :test_indexes, :column1, where: "some_column = 'value'"
end end
def down def down
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment