strong_migrations 1.4.4 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,7 +10,7 @@ module StrongMigrations
10
10
  if !new_table?(table)
11
11
  if postgresql? && options[:validate] != false
12
12
  add_options = options.merge(validate: false)
13
- name = options[:name] || @migration.check_constraint_options(table, expression, options)[:name]
13
+ name = options[:name] || connection.check_constraint_options(table, expression, options)[:name]
14
14
  validate_options = {name: name}
15
15
 
16
16
  if StrongMigrations.safe_by_default
@@ -32,33 +32,27 @@ module StrongMigrations
32
32
  table, column, type = args
33
33
  default = options[:default]
34
34
 
35
+ # keep track of new columns of change_column_default check
36
+ @new_columns << [table.to_s, column.to_s]
37
+
35
38
  # Check key since DEFAULT NULL behaves differently from no default
36
39
  #
37
40
  # Also, Active Record has special case for uuid columns that allows function default values
38
41
  # https://github.com/rails/rails/blob/v7.0.3.1/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb#L92-L93
39
- if options.key?(:default) && (!adapter.add_column_default_safe? || (volatile = (postgresql? && type.to_s == "uuid" && default.to_s.include?("()") && adapter.default_volatile?(default))))
42
+ if !default.nil? && (!adapter.add_column_default_safe? || (volatile = (postgresql? && type.to_s == "uuid" && default.to_s.include?("()") && adapter.default_volatile?(default))))
40
43
  if options[:null] == false
41
44
  options = options.except(:null)
42
- append = "
43
-
44
- Then add the NOT NULL constraint in separate migrations."
45
+ append = "\n\nThen add the NOT NULL constraint in separate migrations."
45
46
  end
46
47
 
47
- if default.nil?
48
- raise_error :add_column_default_null,
49
- command: command_str("add_column", [table, column, type, options.except(:default)]),
50
- append: append,
51
- rewrite_blocks: adapter.rewrite_blocks
52
- else
53
- raise_error :add_column_default,
54
- add_command: command_str("add_column", [table, column, type, options.except(:default)]),
55
- change_command: command_str("change_column_default", [table, column, default]),
56
- remove_command: command_str("remove_column", [table, column]),
57
- code: backfill_code(table, column, default, volatile),
58
- append: append,
59
- rewrite_blocks: adapter.rewrite_blocks,
60
- default_type: (volatile ? "volatile" : "non-null")
61
- end
48
+ raise_error :add_column_default,
49
+ add_command: command_str("add_column", [table, column, type, options.except(:default)]),
50
+ change_command: command_str("change_column_default", [table, column, default]),
51
+ remove_command: command_str("remove_column", [table, column]),
52
+ code: backfill_code(table, column, default, volatile),
53
+ append: append,
54
+ rewrite_blocks: adapter.rewrite_blocks,
55
+ default_type: (volatile ? "volatile" : "non-null")
62
56
  elsif default.is_a?(Proc) && postgresql?
63
57
  # adding a column with a VOLATILE default is not safe
64
58
  # https://www.postgresql.org/docs/current/sql-altertable.html#SQL-ALTERTABLE-NOTES
@@ -71,6 +65,17 @@ Then add the NOT NULL constraint in separate migrations."
71
65
  raise_error :add_column_json,
72
66
  command: command_str("add_column", [table, column, :jsonb, options])
73
67
  end
68
+
69
+ if type.to_s == "virtual" && options[:stored]
70
+ raise_error :add_column_generated_stored, rewrite_blocks: adapter.rewrite_blocks
71
+ end
72
+
73
+ if adapter.auto_incrementing_types.include?(type.to_s)
74
+ append = (mysql? || mariadb?) ? "\n\nIf using statement-based replication, this can also generate different values on replicas." : ""
75
+ raise_error :add_column_auto_incrementing,
76
+ rewrite_blocks: adapter.rewrite_blocks,
77
+ append: append
78
+ end
74
79
  end
75
80
 
76
81
  def check_add_exclusion_constraint(*args)
@@ -147,7 +152,7 @@ Then add the NOT NULL constraint in separate migrations."
147
152
  if bad_index || options[:foreign_key]
148
153
  if index_value.is_a?(Hash)
149
154
  options[:index] = options[:index].merge(algorithm: :concurrently)
150
- else
155
+ elsif index_value
151
156
  options = options.merge(index: {algorithm: :concurrently})
152
157
  end
153
158
 
@@ -158,9 +163,7 @@ Then add the NOT NULL constraint in separate migrations."
158
163
 
159
164
  if options.delete(:foreign_key)
160
165
  headline = "Adding a foreign key blocks writes on both tables."
161
- append = "
162
-
163
- Then add the foreign key in separate migrations."
166
+ append = "\n\nThen add the foreign key in separate migrations."
164
167
  else
165
168
  headline = "Adding an index non-concurrently locks the table."
166
169
  end
@@ -173,6 +176,21 @@ Then add the foreign key in separate migrations."
173
176
  end
174
177
  end
175
178
 
179
+ def check_add_unique_constraint(*args)
180
+ args.extract_options!
181
+ table, column = args
182
+
183
+ # column and using_index cannot be used together
184
+ # check for column to ensure error message can be generated
185
+ if column && !new_table?(table)
186
+ index_name = connection.index_name(table, {column: column})
187
+ raise_error :add_unique_constraint,
188
+ index_command: command_str(:add_index, [table, column, {unique: true, algorithm: :concurrently}]),
189
+ constraint_command: command_str(:add_unique_constraint, [table, {using_index: index_name}]),
190
+ remove_command: command_str(:remove_unique_constraint, [table, column])
191
+ end
192
+ end
193
+
176
194
  def check_change_column(*args)
177
195
  options = args.extract_options!
178
196
  table, column, type = args
@@ -192,66 +210,84 @@ Then add the foreign key in separate migrations."
192
210
  end
193
211
 
194
212
  raise_error :change_column, rewrite_blocks: adapter.rewrite_blocks unless safe
213
+
214
+ # constraints must be rechecked
215
+ # Postgres recommends dropping constraints before and adding them back
216
+ # https://www.postgresql.org/docs/current/ddl-alter.html#DDL-ALTER-COLUMN-TYPE
217
+ if postgresql?
218
+ constraints = adapter.constraints(table, column)
219
+ if constraints.any?
220
+ change_commands = []
221
+ constraints.each do |c|
222
+ change_commands << command_str(:remove_check_constraint, [table, c.expression, {name: c.name}])
223
+ end
224
+ change_commands << command_str(:change_column, args + [options])
225
+ constraints.each do |c|
226
+ change_commands << command_str(:add_check_constraint, [table, c.expression, {name: c.name, validate: false}])
227
+ end
228
+
229
+ validate_commands = []
230
+ constraints.each do |c|
231
+ validate_commands << command_str(:validate_check_constraint, [table, {name: c.name}])
232
+ end
233
+
234
+ raise_error :change_column_constraint,
235
+ change_column_code: change_commands.join("\n "),
236
+ validate_constraint_code: validate_commands.join("\n ")
237
+ end
238
+ end
239
+ end
240
+
241
+ def check_change_column_default(*args)
242
+ table, column, _default_or_changes = args
243
+
244
+ # just check ActiveRecord::Base, even though can override on model
245
+ partial_inserts = ActiveRecord::Base.partial_inserts
246
+
247
+ if partial_inserts && !new_column?(table, column)
248
+ raise_error :change_column_default,
249
+ config: "partial_inserts"
250
+ end
195
251
  end
196
252
 
197
253
  def check_change_column_null(*args)
198
254
  table, column, null, default = args
199
255
  if !null
200
256
  if postgresql?
201
- safe = false
202
- safe_with_check_constraint = adapter.server_version >= Gem::Version.new("12")
203
- if safe_with_check_constraint
204
- safe = adapter.constraints(table).any? { |c| c["def"] == "CHECK ((#{column} IS NOT NULL))" || c["def"] == "CHECK ((#{connection.quote_column_name(column)} IS NOT NULL))" }
205
- end
257
+ constraints = connection.check_constraints(table)
258
+ safe = constraints.any? { |c| c.options[:validate] && (c.expression == "#{column} IS NOT NULL" || c.expression == "#{connection.quote_column_name(column)} IS NOT NULL") }
206
259
 
207
260
  unless safe
261
+ expression = "#{quote_column_if_needed(column)} IS NOT NULL"
262
+
208
263
  # match https://github.com/nullobject/rein
209
264
  constraint_name = "#{table}_#{column}_null"
265
+ if adapter.max_constraint_name_length && constraint_name.bytesize > adapter.max_constraint_name_length
266
+ constraint_name = connection.check_constraint_options(table, expression, {})[:name]
210
267
 
211
- add_code = constraint_str("ALTER TABLE %s ADD CONSTRAINT %s CHECK (%s IS NOT NULL) NOT VALID", [table, constraint_name, column])
212
- validate_code = constraint_str("ALTER TABLE %s VALIDATE CONSTRAINT %s", [table, constraint_name])
213
- remove_code = constraint_str("ALTER TABLE %s DROP CONSTRAINT %s", [table, constraint_name])
214
-
215
- constraint_methods = ar_version >= 6.1
216
-
217
- validate_constraint_code =
218
- if constraint_methods
219
- String.new(command_str(:validate_check_constraint, [table, {name: constraint_name}]))
220
- else
221
- String.new(safety_assured_str(validate_code))
222
- end
223
-
224
- if safe_with_check_constraint
225
- change_args = [table, column, null]
226
-
227
- validate_constraint_code << "\n #{command_str(:change_column_null, change_args)}"
228
-
229
- if constraint_methods
230
- validate_constraint_code << "\n #{command_str(:remove_check_constraint, [table, {name: constraint_name}])}"
231
- else
232
- validate_constraint_code << "\n #{safety_assured_str(remove_code)}"
268
+ # avoid collision with Active Record naming for safe_by_default
269
+ if StrongMigrations.safe_by_default
270
+ constraint_name = constraint_name.sub("rails", "strong_migrations")
233
271
  end
234
272
  end
235
273
 
274
+ add_args = [table, expression, {name: constraint_name, validate: false}]
275
+ validate_args = [table, {name: constraint_name}]
276
+ change_args = [table, column, null]
277
+ remove_args = [table, {name: constraint_name}]
278
+
236
279
  if StrongMigrations.safe_by_default
237
- safe_change_column_null(add_code, validate_code, change_args, remove_code, default)
280
+ safe_change_column_null(add_args, validate_args, change_args, remove_args, table, column, default, constraints)
238
281
  throw :safe
239
282
  end
240
283
 
241
- add_constraint_code =
242
- if constraint_methods
243
- command_str(:add_check_constraint, [table, "#{quote_column_if_needed(column)} IS NOT NULL", {name: constraint_name, validate: false}])
244
- else
245
- safety_assured_str(add_code)
246
- end
284
+ add_constraint_code = command_str(:add_check_constraint, add_args)
247
285
 
248
- validate_constraint_code =
249
- if safe_with_check_constraint
250
- down_code = "#{add_constraint_code}\n #{command_str(:change_column_null, [table, column, true])}"
251
- "def up\n #{validate_constraint_code}\n end\n\n def down\n #{down_code}\n end"
252
- else
253
- "def change\n #{validate_constraint_code}\n end"
254
- end
286
+ up_code = String.new(command_str(:validate_check_constraint, validate_args))
287
+ up_code << "\n #{command_str(:change_column_null, change_args)}"
288
+ up_code << "\n #{command_str(:remove_check_constraint, remove_args)}"
289
+ down_code = "#{add_constraint_code}\n #{command_str(:change_column_null, [table, column, true])}"
290
+ validate_constraint_code = "def up\n #{up_code}\n end\n\n def down\n #{down_code}\n end"
255
291
 
256
292
  raise_error :change_column_null_postgresql,
257
293
  add_constraint_code: add_constraint_code,
@@ -300,29 +336,28 @@ Then add the foreign key in separate migrations."
300
336
  columns =
301
337
  case method
302
338
  when :remove_timestamps
303
- ["created_at", "updated_at"]
339
+ [:created_at, :updated_at]
304
340
  when :remove_column
305
- [args[1].to_s]
341
+ [args[1]]
306
342
  when :remove_columns
307
- # Active Record 6.1+ supports options
308
343
  if args.last.is_a?(Hash)
309
- args[1..-2].map(&:to_s)
344
+ args[1..-2]
310
345
  else
311
- args[1..-1].map(&:to_s)
346
+ args[1..-1]
312
347
  end
313
348
  else
314
349
  options = args[2] || {}
315
350
  reference = args[1]
316
351
  cols = []
317
- cols << "#{reference}_type" if options[:polymorphic]
318
- cols << "#{reference}_id"
352
+ cols << "#{reference}_type".to_sym if options[:polymorphic]
353
+ cols << "#{reference}_id".to_sym
319
354
  cols
320
355
  end
321
356
 
322
- code = "self.ignored_columns = #{columns.inspect}"
357
+ code = "self.ignored_columns += #{columns.map(&:to_s).inspect}"
323
358
 
324
359
  raise_error :remove_column,
325
- model: args[0].to_s.classify,
360
+ model: model_name(args[0]),
326
361
  code: code,
327
362
  command: command_str(method, args),
328
363
  column_suffix: columns.size > 1 ? "s" : ""
@@ -336,12 +371,6 @@ Then add the foreign key in separate migrations."
336
371
  # avoid suggesting extra (invalid) args
337
372
  args = args[0..1] unless StrongMigrations.safe_by_default
338
373
 
339
- # Active Record < 6.1 only supports two arguments (including options)
340
- if args.size == 2 && ar_version < 6.1
341
- # arg takes precedence over option
342
- options[:column] = args.pop
343
- end
344
-
345
374
  if StrongMigrations.safe_by_default
346
375
  safe_remove_index(*args, **options)
347
376
  throw :safe
@@ -355,6 +384,10 @@ Then add the foreign key in separate migrations."
355
384
  raise_error :rename_column
356
385
  end
357
386
 
387
+ def check_rename_schema
388
+ raise_error :rename_schema
389
+ end
390
+
358
391
  def check_rename_table
359
392
  raise_error :rename_table
360
393
  end
@@ -396,7 +429,7 @@ Then add the foreign key in separate migrations."
396
429
  message = message + append if append
397
430
 
398
431
  vars[:migration_name] = @migration.class.name
399
- vars[:migration_suffix] = "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
432
+ vars[:migration_suffix] = migration_suffix
400
433
  vars[:base_model] = "ApplicationRecord"
401
434
 
402
435
  # escape % not followed by {
@@ -437,24 +470,36 @@ Then add the foreign key in separate migrations."
437
470
  end
438
471
 
439
472
  def backfill_code(table, column, default, function = false)
440
- model = table.to_s.classify
473
+ model = model_name(table)
441
474
  if function
442
475
  # update_all(column: Arel.sql(default)) also works in newer versions of Active Record
443
476
  update_expr = "#{quote_column_if_needed(column)} = #{default}"
444
- "#{model}.unscoped.in_batches do |relation| \n relation.where(#{column}: nil).update_all(#{update_expr.inspect})\n sleep(0.01)\n end"
477
+ "#{model}.unscoped.in_batches(of: 10000) do |relation| \n relation.where(#{column}: nil).update_all(#{update_expr.inspect})\n sleep(0.01)\n end"
445
478
  else
446
- "#{model}.unscoped.in_batches do |relation| \n relation.update_all #{column}: #{default.inspect}\n sleep(0.01)\n end"
479
+ "#{model}.unscoped.in_batches(of: 10000) do |relation| \n relation.where(#{column}: nil).update_all #{column}: #{default.inspect}\n sleep(0.01)\n end"
447
480
  end
448
481
  end
449
482
 
450
483
  # only quote when needed
451
484
  # important! only use for display purposes
452
485
  def quote_column_if_needed(column)
453
- column.to_s =~ /\A[a-z0-9_]+\z/ ? column : connection.quote_column_name(column)
486
+ /\A[a-z0-9_]+\z/.match?(column.to_s) ? column : connection.quote_column_name(column)
454
487
  end
455
488
 
456
489
  def new_table?(table)
457
490
  @new_tables.include?(table.to_s)
458
491
  end
492
+
493
+ def new_column?(table, column)
494
+ new_table?(table) || @new_columns.include?([table.to_s, column.to_s])
495
+ end
496
+
497
+ def migration_suffix
498
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
499
+ end
500
+
501
+ def model_name(table)
502
+ table.to_s.classify
503
+ end
459
504
  end
460
505
  end
@@ -25,16 +25,6 @@ class Backfill%{migration_name} < ActiveRecord::Migration%{migration_suffix}
25
25
  end
26
26
  end",
27
27
 
28
- add_column_default_null:
29
- "Adding a column with a null default blocks %{rewrite_blocks} while the entire table is rewritten.
30
- Instead, add the column without a default value.
31
-
32
- class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
33
- def change
34
- %{command}
35
- end
36
- end",
37
-
38
28
  add_column_default_callable:
39
29
  "Strong Migrations does not support inspecting callable default values.
40
30
  Please make really sure you're not calling a VOLATILE function,
@@ -50,6 +40,12 @@ class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
50
40
  end
51
41
  end",
52
42
 
43
+ add_column_generated_stored:
44
+ "Adding a stored generated column blocks %{rewrite_blocks} while the entire table is rewritten.",
45
+
46
+ add_column_auto_incrementing:
47
+ "Adding an auto-incrementing column blocks %{rewrite_blocks} while the entire table is rewritten.",
48
+
53
49
  change_column:
54
50
  "Changing the type of an existing column blocks %{rewrite_blocks}
55
51
  while the entire table is rewritten. A safer approach is to:
@@ -64,6 +60,22 @@ while the entire table is rewritten. A safer approach is to:
64
60
  change_column_with_not_null:
65
61
  "Changing the type is safe, but setting NOT NULL is not.",
66
62
 
63
+ change_column_constraint: "Changing the type of a column that has check constraints blocks reads and writes
64
+ while every row is checked. Drop the check constraints on the column before
65
+ changing the type and add them back afterwards.
66
+
67
+ class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
68
+ def change
69
+ %{change_column_code}
70
+ end
71
+ end
72
+
73
+ class Validate%{migration_name} < ActiveRecord::Migration%{migration_suffix}
74
+ def change
75
+ %{validate_constraint_code}
76
+ end
77
+ end",
78
+
67
79
  remove_column: "Active Record caches attributes, which causes problems
68
80
  when removing columns. Be sure to ignore the column%{column_suffix}:
69
81
 
@@ -85,18 +97,29 @@ in your application. A safer approach is to:
85
97
 
86
98
  1. Create a new column
87
99
  2. Write to both columns
88
- 3. Backfill data from the old column to new column
100
+ 3. Backfill data from the old column to the new column
89
101
  4. Move reads from the old column to the new column
90
102
  5. Stop writing to the old column
91
103
  6. Drop the old column",
92
104
 
105
+ rename_schema:
106
+ "Renaming a schema that's in use will cause errors
107
+ in your application. A safer approach is to:
108
+
109
+ 1. Create a new schema
110
+ 2. Write to both schemas
111
+ 3. Backfill data from the old schema to the new schema
112
+ 4. Move reads from the old schema to the new schema
113
+ 5. Stop writing to the old schema
114
+ 6. Drop the old schema",
115
+
93
116
  rename_table:
94
117
  "Renaming a table that's in use will cause errors
95
118
  in your application. A safer approach is to:
96
119
 
97
120
  1. Create a new table. Don't forget to recreate indexes from the old table
98
121
  2. Write to both tables
99
- 3. Backfill data from the old table to new table
122
+ 3. Backfill data from the old table to the new table
100
123
  4. Move reads from the old table to the new table
101
124
  5. Stop writing to the old table
102
125
  6. Drop the old table",
@@ -151,13 +174,20 @@ you're doing is safe before proceeding, then wrap it in a safety_assured { ... }
151
174
  create_table:
152
175
  "The force option will destroy existing tables.
153
176
  If this is intended, drop the existing table first.
154
- Otherwise, remove the force option.",
177
+ In any case, remove the force option.",
155
178
 
156
179
  execute:
157
180
  "Strong Migrations does not support inspecting what happens inside an
158
181
  execute call, so cannot help you here. Please make really sure that what
159
182
  you're doing is safe before proceeding, then wrap it in a safety_assured { ... } block.",
160
183
 
184
+ change_column_default:
185
+ "Partial writes are enabled, which can cause incorrect values
186
+ to be inserted when changing the default value of a column.
187
+ Disable partial writes in config/application.rb:
188
+
189
+ config.active_record.%{config} = false",
190
+
161
191
  change_column_null:
162
192
  "Passing a default value to change_column_null runs a single UPDATE query,
163
193
  which can cause downtime. Instead, backfill the existing rows in the
@@ -234,7 +264,24 @@ end",
234
264
  Use disable_ddl_transaction! or a separate migration.",
235
265
 
236
266
  add_exclusion_constraint:
237
- "Adding an exclusion constraint blocks reads and writes while every row is checked."
267
+ "Adding an exclusion constraint blocks reads and writes while every row is checked.",
268
+
269
+ add_unique_constraint:
270
+ "Adding a unique constraint creates a unique index, which blocks reads and writes.
271
+ Instead, create a unique index concurrently, then use it for the constraint.
272
+
273
+ class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
274
+ disable_ddl_transaction!
275
+
276
+ def up
277
+ %{index_command}
278
+ %{constraint_command}
279
+ end
280
+
281
+ def down
282
+ %{remove_command}
283
+ end
284
+ end"
238
285
  }
239
286
  self.enabled_checks = (error_messages.keys - [:remove_index]).map { |k| [k, {}] }.to_h
240
287
  end
@@ -7,10 +7,7 @@ module StrongMigrations
7
7
  end
8
8
 
9
9
  def method_missing(method, *args)
10
- return super if is_a?(ActiveRecord::Schema)
11
-
12
- # Active Record 7.0.2+ versioned schema
13
- return super if defined?(ActiveRecord::Schema::Definition) && is_a?(ActiveRecord::Schema::Definition)
10
+ return super if is_a?(ActiveRecord::Schema) || is_a?(ActiveRecord::Schema::Definition)
14
11
 
15
12
  catch(:safe) do
16
13
  strong_migrations_checker.perform(method, *args) do
@@ -18,10 +15,19 @@ module StrongMigrations
18
15
  end
19
16
  end
20
17
  end
21
- ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
18
+ # same as ActiveRecord::Migration
19
+ ruby2_keywords(:method_missing)
20
+
21
+ def revert(*)
22
+ if strong_migrations_checker.version_safe?
23
+ safety_assured { super }
24
+ else
25
+ super
26
+ end
27
+ end
22
28
 
23
29
  def safety_assured
24
- strong_migrations_checker.safety_assured do
30
+ strong_migrations_checker.class.safety_assured do
25
31
  yield
26
32
  end
27
33
  end
@@ -1,9 +1,26 @@
1
1
  module StrongMigrations
2
- module DatabaseTasks
3
- # Active Record 7 adds version argument
4
- def migrate(*args)
2
+ module MigrationContext
3
+ def up(...)
5
4
  super
6
5
  rescue => e
6
+ strong_migrations_process_exception(e)
7
+ end
8
+
9
+ def down(...)
10
+ super
11
+ rescue => e
12
+ strong_migrations_process_exception(e)
13
+ end
14
+
15
+ def run(...)
16
+ super
17
+ rescue => e
18
+ strong_migrations_process_exception(e)
19
+ end
20
+
21
+ private
22
+
23
+ def strong_migrations_process_exception(e)
7
24
  if e.cause.is_a?(StrongMigrations::Error)
8
25
  # strip cause and clean backtrace
9
26
  def e.cause
@@ -1,18 +1,20 @@
1
1
  module StrongMigrations
2
2
  module Migrator
3
- def ddl_transaction(migration, *args)
3
+ def ddl_transaction(migration, ...)
4
4
  return super unless StrongMigrations.lock_timeout_retries > 0 && use_transaction?(migration)
5
5
 
6
6
  # handle MigrationProxy class
7
- migration = migration.send(:migration) if migration.respond_to?(:migration, true)
7
+ migration = migration.send(:migration) if !migration.is_a?(ActiveRecord::Migration) && migration.respond_to?(:migration, true)
8
8
 
9
- # retry migration since the entire transaction needs to be rerun
10
9
  checker = migration.send(:strong_migrations_checker)
10
+ return super if checker.skip?
11
+
12
+ # retry migration since the entire transaction needs to be rerun
11
13
  checker.retry_lock_timeouts(check_committed: true) do
12
14
  # failed transaction reverts timeout, so need to re-apply
13
- checker.timeouts_set = false
15
+ checker.reset
14
16
 
15
- super(migration, *args)
17
+ super(migration, ...)
16
18
  end
17
19
  end
18
20
  end