lib/strong_migrations/checker.rb in strong_migrations-0.4.2 vs lib/strong_migrations/checker.rb in strong_migrations-0.5.0
- old
+ new
@@ -57,13 +57,23 @@
options ||= {}
if columns.is_a?(Array) && columns.size > 3 && !options[:unique]
raise_error :add_index_columns, header: "Best practice"
end
- if postgresql? && options[:algorithm] != :concurrently && !@new_tables.include?(table.to_s)
+ if postgresql? && options[:algorithm] != :concurrently && !new_table?(table)
raise_error :add_index, command: command_str("add_index", [table, columns, options.merge(algorithm: :concurrently)])
end
+ when :remove_index
+ table, options = args
+ unless options.is_a?(Hash)
+ options = {column: options}
+ end
+ options ||= {}
+
+ if postgresql? && options[:algorithm] != :concurrently && !new_table?(table)
+ raise_error :remove_index, command: command_str("remove_index", [table, options.merge(algorithm: :concurrently)])
+ end
when :add_column
table, column, type, options = args
options ||= {}
default = options[:default]
@@ -71,17 +81,11 @@
if options[:null] == false
options = options.except(:null)
append = "
-Then add the NOT NULL constraint.
-
-class %{migration_name}NotNull < ActiveRecord::Migration%{migration_suffix}
- def change
- #{command_str("change_column_null", [table, column, false])}
- end
-end"
+Then add the NOT NULL constraint."
end
raise_error :add_column_default,
add_command: command_str("add_column", [table, column, type, options.except(:default)]),
change_command: command_str("change_column_default", [table, column, default]),
@@ -131,13 +135,22 @@
end
when :execute
raise_error :execute, header: "Possibly dangerous operation"
when :change_column_null
table, column, null, default = args
- if !null && !default.nil?
- raise_error :change_column_null,
- code: backfill_code(table, column, default)
+ if !null
+ if postgresql?
+ # match https://github.com/nullobject/rein
+ constraint_name = "#{table}_#{column}_null"
+
+ raise_error :change_column_null_postgresql,
+ add_constraint_code: constraint_str("ALTER TABLE %s ADD CONSTRAINT %s CHECK (%s IS NOT NULL) NOT VALID", [table, constraint_name, column]),
+ validate_constraint_code: constraint_str("ALTER TABLE %s VALIDATE CONSTRAINT %s", [table, constraint_name])
+ elsif !default.nil?
+ raise_error :change_column_null,
+ code: backfill_code(table, column, default)
+ end
end
when :add_foreign_key
from_table, to_table, options = args
options ||= {}
validate = options.fetch(:validate, true)
@@ -157,12 +170,12 @@
column = options[:column] || "#{to_table.to_s.singularize}_id"
hashed_identifier = Digest::SHA256.hexdigest("#{from_table}_#{column}_fk").first(10)
fk_name = options[:name] || "fk_rails_#{hashed_identifier}"
raise_error :add_foreign_key,
- add_foreign_key_code: foreign_key_str("ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s) NOT VALID", [from_table, fk_name, column, to_table, primary_key]),
- validate_foreign_key_code: foreign_key_str("ALTER TABLE %s VALIDATE CONSTRAINT %s", [from_table, fk_name])
+ add_foreign_key_code: constraint_str("ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s) NOT VALID", [from_table, fk_name, column, to_table, primary_key]),
+ validate_foreign_key_code: constraint_str("ALTER TABLE %s VALIDATE CONSTRAINT %s", [from_table, fk_name])
end
end
end
StrongMigrations.checks.each do |check|
@@ -212,10 +225,12 @@
end
end
end
def raise_error(message_key, header: nil, **vars)
+ return unless StrongMigrations.check_enabled?(message_key, version: version)
+
message = StrongMigrations.error_messages[message_key] || "Missing message"
vars[:migration_name] = self.class.name
vars[:migration_suffix] = "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
vars[:base_model] = "ApplicationRecord"
@@ -227,11 +242,11 @@
# escape % not followed by {
@migration.stop!(message.gsub(/%(?!{)/, "%%") % vars, header: header || "Dangerous operation detected")
end
- def foreign_key_str(statement, identifiers)
+ def constraint_str(statement, identifiers)
# not all identifiers are tables, but this method of quoting should be fine
code = statement % identifiers.map { |v| connection.quote_table_name(v) }
"safety_assured do\n execute '#{code}' \n end"
end
@@ -259,8 +274,12 @@
end
def backfill_code(table, column, default)
model = table.to_s.classify
"#{model}.unscoped.in_batches do |relation| \n relation.update_all #{column}: #{default.inspect}\n sleep(0.1)\n end"
+ end
+
+ def new_table?(table)
+ @new_tables.include?(table.to_s)
end
end
end