Added RuboCop cops for checking DB migrations

There are currently two cops for this:

* Migration/AddIndex: checks if indexes are added concurrently
* Migration/ColumnWithDefault: checks if columns with default values are
  added in a concurrent manner

Both cops are fairly simple and make no attempt at correcting the code
as this is quite hard to do (e.g. modifications may need to be applied
in various places in the same file). They however should provide enough
to catch people ignoring the comments in generated migrations telling
them to use add_concurrent_index or add_column_with_default.
This commit is contained in:
Yorick Peterse 2016-06-24 17:26:28 +02:00
parent d33991f8cc
commit c740445ad3
No known key found for this signature in database
GPG Key ID: EDD30D2BEB691AC9
5 changed files with 112 additions and 1 deletions

View File

@ -1,4 +1,6 @@
require: rubocop-rspec
require:
- rubocop-rspec
- ./rubocop/rubocop
AllCops:
TargetRubyVersion: 2.1

View File

@ -0,0 +1,46 @@
module RuboCop
module Cop
module Migration
# Cop that checks if indexes are added in a concurrent manner.
class AddIndex < RuboCop::Cop::Cop
include MigrationHelpers
MSG = 'add_index requires downtime, use add_concurrent_index instead'
def on_def(node)
return unless in_migration?(node)
new_tables = []
node.each_descendant(:send) do |send_node|
first_arg = first_argument(send_node)
# The first argument of "create_table" / "add_index" is the table
# name.
new_tables << first_arg if create_table?(send_node)
next if method_name(send_node) != :add_index
# Using "add_index" is fine for newly created tables as there's no
# data in these tables yet.
next if new_tables.include?(first_arg)
add_offense(send_node, :selector)
end
end
def create_table?(node)
method_name(node) == :create_table
end
def method_name(node)
node.children[1]
end
def first_argument(node)
node.children[2] ? node.children[0] : nil
end
end
end
end
end

View File

@ -0,0 +1,50 @@
module RuboCop
module Cop
module Migration
# Cop that checks if columns are added in a way that doesn't require
# downtime.
class ColumnWithDefault < RuboCop::Cop::Cop
include MigrationHelpers
WHITELISTED_TABLES = [:application_settings]
MSG = 'add_column with a default value requires downtime, ' \
'use add_column_with_default instead'
def on_send(node)
return unless in_migration?(node)
name = node.children[1]
return unless name == :add_column
# Ignore whitelisted tables.
return if table_whitelisted?(node.children[2])
opts = node.children.last
return unless opts && opts.type == :hash
opts.each_node(:pair) do |pair|
if hash_key_type(pair) == :sym && hash_key_name(pair) == :default
add_offense(node, :selector)
end
end
end
def table_whitelisted?(symbol)
symbol && symbol.type == :sym &&
WHITELISTED_TABLES.include?(symbol.children[0])
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

View File

@ -0,0 +1,10 @@
module RuboCop
# Module containing helper methods for writing migration cops.
module MigrationHelpers
# Returns true if the given node originated from the db/migrate directory.
def in_migration?(node)
File.dirname(node.location.expression.source_buffer.name).
end_with?('db/migrate')
end
end
end

3
rubocop/rubocop.rb Normal file
View File

@ -0,0 +1,3 @@
require_relative 'migration_helpers'
require_relative 'cop/migration/add_index'
require_relative 'cop/migration/column_with_default'