lib/strong_migrations/checks.rb in strong_migrations-1.2.0 vs lib/strong_migrations/checks.rb in strong_migrations-1.3.0
- old
+ new
@@ -30,11 +30,13 @@
def check_add_column(*args)
options = args.extract_options!
table, column, type = args
default = options[:default]
- if !default.nil? && !adapter.add_column_default_safe?
+ # Active Record has special case for uuid columns that allows function default values
+ # https://github.com/rails/rails/blob/v7.0.3.1/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb#L92-L93
+ if !default.nil? && (!adapter.add_column_default_safe? || (volatile = (postgresql? && type.to_s == "uuid" && default.to_s.include?("()") && adapter.default_volatile?(default))))
if options[:null] == false
options = options.except(:null)
append = "
Then add the NOT NULL constraint in separate migrations."
@@ -42,13 +44,14 @@
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]),
remove_command: command_str("remove_column", [table, column]),
- code: backfill_code(table, column, default),
+ code: backfill_code(table, column, default, volatile),
append: append,
- rewrite_blocks: adapter.rewrite_blocks
+ rewrite_blocks: adapter.rewrite_blocks,
+ default_type: (volatile ? "volatile" : "non-null")
elsif default.is_a?(Proc) && postgresql?
# adding a column with a VOLATILE default is not safe
# https://www.postgresql.org/docs/current/sql-altertable.html#SQL-ALTERTABLE-NOTES
# functions like random() and clock_timestamp() are VOLATILE
# check for Proc to match Active Record
@@ -224,13 +227,21 @@
command_str(:add_check_constraint, [table, "#{expr_column} IS NOT NULL", {name: constraint_name, validate: false}])
else
safety_assured_str(add_code)
end
+ validate_constraint_code =
+ if safe_with_check_constraint
+ down_code = "#{add_constraint_code}\n #{command_str(:change_column_null, [table, column, true])}"
+ "def up\n #{validate_constraint_code}\n end\n\n def down\n #{down_code}\n end"
+ else
+ "def change\n #{validate_constraint_code}\n end"
+ end
+
raise_error :change_column_null_postgresql,
add_constraint_code: add_constraint_code,
- validate_constraint_code: "def change\n #{validate_constraint_code}\n end"
+ validate_constraint_code: validate_constraint_code
end
elsif mysql? || mariadb?
unless adapter.strict_mode?
raise_error :change_column_null_mysql
end
@@ -407,12 +418,17 @@
end
"#{command} #{str_args.join(", ")}"
end
- def backfill_code(table, column, default)
+ def backfill_code(table, column, default, function = false)
model = table.to_s.classify
- "#{model}.unscoped.in_batches do |relation| \n relation.update_all #{column}: #{default.inspect}\n sleep(0.01)\n end"
+ if function
+ # update_all(column: Arel.sql(default)) also works in newer versions of Active Record
+ "#{model}.unscoped.in_batches do |relation| \n relation.where(#{column}: nil).update_all(\"#{column} = #{default}\")\n sleep(0.01)\n end"
+ else
+ "#{model}.unscoped.in_batches do |relation| \n relation.update_all #{column}: #{default.inspect}\n sleep(0.01)\n end"
+ end
end
def new_table?(table)
@new_tables.include?(table.to_s)
end