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