# Cop that checks if a foreign key constraint is added and require a index for it
# add_reference can only be used with newly created tables.
# Additionally, the cop here checks that we create an index for the foreign key, too.
classAddReference<RuboCop::Cop::Cop
classAddReference<RuboCop::Cop::Cop
includeMigrationHelpers
includeMigrationHelpers
MSG='`add_reference` requires `index: true` or `index: { options... }`'
MSG='`add_reference` requires downtime for existing tables, use `add_concurrent_foreign_key` instead. When used for new tables, `index: true` or `index: { options... } is required.`'
defon_send(node)
defon_def(node)
returnunlessin_migration?(node)
returnunlessin_migration?(node)
name=node.children[1]
new_tables=[]
returnunlessname==:add_reference
node.each_descendant(:send)do|send_node|
first_arg=first_argument(send_node)
# The first argument of "create_table" / "add_reference" is the table
# name.
new_tables<<first_argifcreate_table?(send_node)
nextifmethod_name(send_node)!=:add_reference
# Using "add_reference" is fine for newly created tables as there's no
let(:offense){'`add_reference` requires downtime for existing tables, use `add_concurrent_foreign_key` instead. When used for new tables, `index: true` or `index: { options... } is required.`'}
context'when the table existed before'do
it'registers an offense when using add_reference'do
expect_offense(<<~RUBY)
def up
add_reference(:projects, :users)
^^^^^^^^^^^^^ #{offense}
end
RUBY
end
it'registers an offense when using add_reference with index enabled'do
expect_offense(<<~RUBY)
def up
add_reference(:projects, :users, index: true)
^^^^^^^^^^^^^ #{offense}
end
RUBY
end
it'registers an offense if only a different table was created'do
expect_offense(<<~RUBY)
def up
create_table(:foo) do |t|
t.string :name
end
add_reference(:projects, :users, index: true)
^^^^^^^^^^^^^ #{offense}
end
RUBY
end
end
context'when creating the table at the same time'do
let(:create_table_statement)do
<<~RUBY
create_table(:projects) do |t|
t.string :name
end
RUBY
end
it'registers an offense when using add_reference without index'do
it'registers an offense when using add_reference without index'do
expect_offense(<<~RUBY)
expect_offense(<<~RUBY)
call do
def up
#{create_table_statement}
add_reference(:projects, :users)
add_reference(:projects, :users)
^^^^^^^^^^^^^ `add_reference` requires `index: true` or `index: { options... }`
^^^^^^^^^^^^^ #{offense}
end
end
RUBY
RUBY
end
end
...
@@ -37,8 +81,9 @@ describe RuboCop::Cop::Migration::AddReference do
...
@@ -37,8 +81,9 @@ describe RuboCop::Cop::Migration::AddReference do
it'registers an offense when using add_reference index disabled'do
it'registers an offense when using add_reference index disabled'do
expect_offense(<<~RUBY)
expect_offense(<<~RUBY)
def up
def up
#{create_table_statement}
add_reference(:projects, :users, index: false)
add_reference(:projects, :users, index: false)
^^^^^^^^^^^^^ `add_reference` requires `index: true` or `index: { options... }`
^^^^^^^^^^^^^ #{offense}
end
end
RUBY
RUBY
end
end
...
@@ -46,6 +91,7 @@ describe RuboCop::Cop::Migration::AddReference do
...
@@ -46,6 +91,7 @@ describe RuboCop::Cop::Migration::AddReference do
it'does not register an offense when using add_reference with index enabled'do
it'does not register an offense when using add_reference with index enabled'do
expect_no_offenses(<<~RUBY)
expect_no_offenses(<<~RUBY)
def up
def up
#{create_table_statement}
add_reference(:projects, :users, index: true)
add_reference(:projects, :users, index: true)
end
end
RUBY
RUBY
...
@@ -54,9 +100,11 @@ describe RuboCop::Cop::Migration::AddReference do
...
@@ -54,9 +100,11 @@ describe RuboCop::Cop::Migration::AddReference do
it'does not register an offense when the index is unique'do
it'does not register an offense when the index is unique'do