strong_migrations 2.0.2 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/LICENSE.txt +1 -1
- data/README.md +74 -41
- data/lib/strong_migrations/adapters/abstract_adapter.rb +5 -2
- data/lib/strong_migrations/adapters/mysql_adapter.rb +4 -0
- data/lib/strong_migrations/adapters/postgresql_adapter.rb +15 -17
- data/lib/strong_migrations/checker.rb +70 -4
- data/lib/strong_migrations/checks.rb +41 -26
- data/lib/strong_migrations/error_messages.rb +1 -1
- data/lib/strong_migrations/migration.rb +2 -1
- data/lib/strong_migrations/{database_tasks.rb → migration_context.rb} +20 -2
- data/lib/strong_migrations/migrator.rb +4 -2
- data/lib/strong_migrations/safe_methods.rb +31 -29
- data/lib/strong_migrations/version.rb +1 -1
- data/lib/strong_migrations.rb +9 -6
- metadata +6 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e155e036c26788c7c9fa7f513131422b784b895fe1cbfdfc8052ad1f5233cd6c
|
4
|
+
data.tar.gz: 5fe21cada2e29c433313c686332d96c67e2c55d057fd6c68839fe1993307c0f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f40abaf84f10ce6ce7255d083b3ec0940c2f7380705857767b0cde56b23802dcf844c27251c634006559dfb4489b799ab3758bf9928b6cb57b6bceb1bf051ec
|
7
|
+
data.tar.gz: c283b8b93715a4aabd2c0e8dc15b022e45ebfbe3bb52fccc037b9c95d3b2279d6002b78354cab8600cf2972f96a6966af0fd80aa9b58cde53343b5035d8bc59c
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
## 2.2.0 (2025-02-01)
|
2
|
+
|
3
|
+
- Fixed constraint name for long table and column names with `change_column_null`
|
4
|
+
- Dropped support for Active Record < 7
|
5
|
+
|
6
|
+
## 2.1.0 (2024-11-08)
|
7
|
+
|
8
|
+
- Added `skip_database` method
|
9
|
+
- Added experimental `remove_invalid_indexes` option
|
10
|
+
- Added warning for unsupported adapters
|
11
|
+
- Improved output for `db:forward`, `db:rollback`, `db:migrate:up`, and `db:migrate:down`
|
12
|
+
- Made operations more retriable with `safe_by_default`
|
13
|
+
|
1
14
|
## 2.0.2 (2024-10-30)
|
2
15
|
|
3
16
|
- Fixed migrations not running with Active Record 8 rc2
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -43,7 +43,7 @@ end
|
|
43
43
|
|
44
44
|
Deploy the code, then wrap this step in a safety_assured { ... } block.
|
45
45
|
|
46
|
-
class RemoveColumn < ActiveRecord::Migration[
|
46
|
+
class RemoveColumn < ActiveRecord::Migration[8.0]
|
47
47
|
def change
|
48
48
|
safety_assured { remove_column :users, :name }
|
49
49
|
end
|
@@ -98,7 +98,7 @@ You can also add [custom checks](#custom-checks) or [disable specific checks](#d
|
|
98
98
|
Active Record caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
|
99
99
|
|
100
100
|
```ruby
|
101
|
-
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[
|
101
|
+
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[8.0]
|
102
102
|
def change
|
103
103
|
remove_column :users, :some_column
|
104
104
|
end
|
@@ -119,7 +119,7 @@ end
|
|
119
119
|
3. Write a migration to remove the column (wrap in `safety_assured` block)
|
120
120
|
|
121
121
|
```ruby
|
122
|
-
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[
|
122
|
+
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[8.0]
|
123
123
|
def change
|
124
124
|
safety_assured { remove_column :users, :some_column }
|
125
125
|
end
|
@@ -136,7 +136,7 @@ end
|
|
136
136
|
Changing the type of a column causes the entire table to be rewritten. During this time, reads and writes are blocked in Postgres, and writes are blocked in MySQL and MariaDB.
|
137
137
|
|
138
138
|
```ruby
|
139
|
-
class ChangeSomeColumnType < ActiveRecord::Migration[
|
139
|
+
class ChangeSomeColumnType < ActiveRecord::Migration[8.0]
|
140
140
|
def change
|
141
141
|
change_column :users, :some_column, :new_type
|
142
142
|
end
|
@@ -182,7 +182,7 @@ A safer approach is to:
|
|
182
182
|
Renaming a column that’s in use will cause errors in your application.
|
183
183
|
|
184
184
|
```ruby
|
185
|
-
class RenameSomeColumn < ActiveRecord::Migration[
|
185
|
+
class RenameSomeColumn < ActiveRecord::Migration[8.0]
|
186
186
|
def change
|
187
187
|
rename_column :users, :some_column, :new_name
|
188
188
|
end
|
@@ -207,7 +207,7 @@ A safer approach is to:
|
|
207
207
|
Renaming a table that’s in use will cause errors in your application.
|
208
208
|
|
209
209
|
```ruby
|
210
|
-
class RenameUsersToCustomers < ActiveRecord::Migration[
|
210
|
+
class RenameUsersToCustomers < ActiveRecord::Migration[8.0]
|
211
211
|
def change
|
212
212
|
rename_table :users, :customers
|
213
213
|
end
|
@@ -232,7 +232,7 @@ A safer approach is to:
|
|
232
232
|
The `force` option can drop an existing table.
|
233
233
|
|
234
234
|
```ruby
|
235
|
-
class CreateUsers < ActiveRecord::Migration[
|
235
|
+
class CreateUsers < ActiveRecord::Migration[8.0]
|
236
236
|
def change
|
237
237
|
create_table :users, force: true do |t|
|
238
238
|
# ...
|
@@ -246,7 +246,7 @@ end
|
|
246
246
|
Create tables without the `force` option.
|
247
247
|
|
248
248
|
```ruby
|
249
|
-
class CreateUsers < ActiveRecord::Migration[
|
249
|
+
class CreateUsers < ActiveRecord::Migration[8.0]
|
250
250
|
def change
|
251
251
|
create_table :users do |t|
|
252
252
|
# ...
|
@@ -264,7 +264,7 @@ If you intend to drop an existing table, run `drop_table` first.
|
|
264
264
|
Adding an auto-incrementing column (`serial`/`bigserial` in Postgres and `AUTO_INCREMENT` in MySQL and MariaDB) causes the entire table to be rewritten. During this time, reads and writes are blocked in Postgres, and writes are blocked in MySQL and MariaDB.
|
265
265
|
|
266
266
|
```ruby
|
267
|
-
class AddIdToCitiesUsers < ActiveRecord::Migration[
|
267
|
+
class AddIdToCitiesUsers < ActiveRecord::Migration[8.0]
|
268
268
|
def change
|
269
269
|
add_column :cities_users, :id, :primary_key
|
270
270
|
end
|
@@ -284,7 +284,7 @@ Create a new table and migrate the data with the same steps as [renaming a table
|
|
284
284
|
Adding a stored generated column causes the entire table to be rewritten. During this time, reads and writes are blocked in Postgres, and writes are blocked in MySQL and MariaDB.
|
285
285
|
|
286
286
|
```ruby
|
287
|
-
class AddSomeColumnToUsers < ActiveRecord::Migration[
|
287
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[8.0]
|
288
288
|
def change
|
289
289
|
add_column :users, :some_column, :virtual, type: :string, as: "...", stored: true
|
290
290
|
end
|
@@ -304,7 +304,7 @@ Add a non-generated column and use callbacks or triggers instead (or a virtual g
|
|
304
304
|
Adding a check constraint blocks reads and writes in Postgres and blocks writes in MySQL and MariaDB while every row is checked.
|
305
305
|
|
306
306
|
```ruby
|
307
|
-
class AddCheckConstraint < ActiveRecord::Migration[
|
307
|
+
class AddCheckConstraint < ActiveRecord::Migration[8.0]
|
308
308
|
def change
|
309
309
|
add_check_constraint :users, "price > 0", name: "price_check"
|
310
310
|
end
|
@@ -316,7 +316,7 @@ end
|
|
316
316
|
Add the check constraint without validating existing rows:
|
317
317
|
|
318
318
|
```ruby
|
319
|
-
class AddCheckConstraint < ActiveRecord::Migration[
|
319
|
+
class AddCheckConstraint < ActiveRecord::Migration[8.0]
|
320
320
|
def change
|
321
321
|
add_check_constraint :users, "price > 0", name: "price_check", validate: false
|
322
322
|
end
|
@@ -326,7 +326,7 @@ end
|
|
326
326
|
Then validate them in a separate migration.
|
327
327
|
|
328
328
|
```ruby
|
329
|
-
class ValidateCheckConstraint < ActiveRecord::Migration[
|
329
|
+
class ValidateCheckConstraint < ActiveRecord::Migration[8.0]
|
330
330
|
def change
|
331
331
|
validate_check_constraint :users, name: "price_check"
|
332
332
|
end
|
@@ -342,7 +342,7 @@ end
|
|
342
342
|
Strong Migrations can’t ensure safety for raw SQL statements. Make really sure that what you’re doing is safe, then use:
|
343
343
|
|
344
344
|
```ruby
|
345
|
-
class ExecuteSQL < ActiveRecord::Migration[
|
345
|
+
class ExecuteSQL < ActiveRecord::Migration[8.0]
|
346
346
|
def change
|
347
347
|
safety_assured { execute "..." }
|
348
348
|
end
|
@@ -351,12 +351,14 @@ end
|
|
351
351
|
|
352
352
|
### Backfilling data
|
353
353
|
|
354
|
+
Note: Strong Migrations does not detect dangerous backfills.
|
355
|
+
|
354
356
|
#### Bad
|
355
357
|
|
356
358
|
Active Record creates a transaction around each migration, and backfilling in the same transaction that alters a table keeps the table locked for the [duration of the backfill](https://wework.github.io/data/2015/11/05/add-columns-with-default-values-to-large-tables-in-rails-postgres/).
|
357
359
|
|
358
360
|
```ruby
|
359
|
-
class AddSomeColumnToUsers < ActiveRecord::Migration[
|
361
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[8.0]
|
360
362
|
def change
|
361
363
|
add_column :users, :some_column, :text
|
362
364
|
User.update_all some_column: "default_value"
|
@@ -371,7 +373,7 @@ Also, running a single query to update data can cause issues for large tables.
|
|
371
373
|
There are three keys to backfilling safely: batching, throttling, and running it outside a transaction. Use the Rails console or a separate migration with `disable_ddl_transaction!`.
|
372
374
|
|
373
375
|
```ruby
|
374
|
-
class BackfillSomeColumn < ActiveRecord::Migration[
|
376
|
+
class BackfillSomeColumn < ActiveRecord::Migration[8.0]
|
375
377
|
disable_ddl_transaction!
|
376
378
|
|
377
379
|
def up
|
@@ -383,6 +385,8 @@ class BackfillSomeColumn < ActiveRecord::Migration[7.2]
|
|
383
385
|
end
|
384
386
|
```
|
385
387
|
|
388
|
+
Note: If backfilling with a method other than `update_all`, use `User.reset_column_information` to ensure the model has up-to-date column information.
|
389
|
+
|
386
390
|
### Adding an index non-concurrently
|
387
391
|
|
388
392
|
:turtle: Safe by default available
|
@@ -392,7 +396,7 @@ end
|
|
392
396
|
In Postgres, adding an index non-concurrently blocks writes.
|
393
397
|
|
394
398
|
```ruby
|
395
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[
|
399
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[8.0]
|
396
400
|
def change
|
397
401
|
add_index :users, :some_column
|
398
402
|
end
|
@@ -404,7 +408,7 @@ end
|
|
404
408
|
Add indexes concurrently.
|
405
409
|
|
406
410
|
```ruby
|
407
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[
|
411
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[8.0]
|
408
412
|
disable_ddl_transaction!
|
409
413
|
|
410
414
|
def change
|
@@ -430,7 +434,7 @@ rails g index table column
|
|
430
434
|
Rails adds an index non-concurrently to references by default, which blocks writes in Postgres.
|
431
435
|
|
432
436
|
```ruby
|
433
|
-
class AddReferenceToUsers < ActiveRecord::Migration[
|
437
|
+
class AddReferenceToUsers < ActiveRecord::Migration[8.0]
|
434
438
|
def change
|
435
439
|
add_reference :users, :city
|
436
440
|
end
|
@@ -442,7 +446,7 @@ end
|
|
442
446
|
Make sure the index is added concurrently.
|
443
447
|
|
444
448
|
```ruby
|
445
|
-
class AddReferenceToUsers < ActiveRecord::Migration[
|
449
|
+
class AddReferenceToUsers < ActiveRecord::Migration[8.0]
|
446
450
|
disable_ddl_transaction!
|
447
451
|
|
448
452
|
def change
|
@@ -460,7 +464,7 @@ end
|
|
460
464
|
In Postgres, adding a foreign key blocks writes on both tables.
|
461
465
|
|
462
466
|
```ruby
|
463
|
-
class AddForeignKeyOnUsers < ActiveRecord::Migration[
|
467
|
+
class AddForeignKeyOnUsers < ActiveRecord::Migration[8.0]
|
464
468
|
def change
|
465
469
|
add_foreign_key :users, :orders
|
466
470
|
end
|
@@ -470,7 +474,7 @@ end
|
|
470
474
|
or
|
471
475
|
|
472
476
|
```ruby
|
473
|
-
class AddReferenceToUsers < ActiveRecord::Migration[
|
477
|
+
class AddReferenceToUsers < ActiveRecord::Migration[8.0]
|
474
478
|
def change
|
475
479
|
add_reference :users, :order, foreign_key: true
|
476
480
|
end
|
@@ -482,7 +486,7 @@ end
|
|
482
486
|
Add the foreign key without validating existing rows:
|
483
487
|
|
484
488
|
```ruby
|
485
|
-
class AddForeignKeyOnUsers < ActiveRecord::Migration[
|
489
|
+
class AddForeignKeyOnUsers < ActiveRecord::Migration[8.0]
|
486
490
|
def change
|
487
491
|
add_foreign_key :users, :orders, validate: false
|
488
492
|
end
|
@@ -492,7 +496,7 @@ end
|
|
492
496
|
Then validate them in a separate migration.
|
493
497
|
|
494
498
|
```ruby
|
495
|
-
class ValidateForeignKeyOnUsers < ActiveRecord::Migration[
|
499
|
+
class ValidateForeignKeyOnUsers < ActiveRecord::Migration[8.0]
|
496
500
|
def change
|
497
501
|
validate_foreign_key :users, :orders
|
498
502
|
end
|
@@ -506,7 +510,7 @@ end
|
|
506
510
|
In Postgres, adding a unique constraint creates a unique index, which blocks reads and writes.
|
507
511
|
|
508
512
|
```ruby
|
509
|
-
class AddUniqueConstraint < ActiveRecord::Migration[
|
513
|
+
class AddUniqueConstraint < ActiveRecord::Migration[8.0]
|
510
514
|
def change
|
511
515
|
add_unique_constraint :users, :some_column
|
512
516
|
end
|
@@ -518,7 +522,7 @@ end
|
|
518
522
|
Create a unique index concurrently, then use it for the constraint.
|
519
523
|
|
520
524
|
```ruby
|
521
|
-
class AddUniqueConstraint < ActiveRecord::Migration[
|
525
|
+
class AddUniqueConstraint < ActiveRecord::Migration[8.0]
|
522
526
|
disable_ddl_transaction!
|
523
527
|
|
524
528
|
def up
|
@@ -539,7 +543,7 @@ end
|
|
539
543
|
In Postgres, adding an exclusion constraint blocks reads and writes while every row is checked.
|
540
544
|
|
541
545
|
```ruby
|
542
|
-
class AddExclusionConstraint < ActiveRecord::Migration[
|
546
|
+
class AddExclusionConstraint < ActiveRecord::Migration[8.0]
|
543
547
|
def change
|
544
548
|
add_exclusion_constraint :users, "number WITH =", using: :gist
|
545
549
|
end
|
@@ -557,7 +561,7 @@ end
|
|
557
561
|
In Postgres, there’s no equality operator for the `json` column type, which can cause errors for existing `SELECT DISTINCT` queries in your application.
|
558
562
|
|
559
563
|
```ruby
|
560
|
-
class AddPropertiesToUsers < ActiveRecord::Migration[
|
564
|
+
class AddPropertiesToUsers < ActiveRecord::Migration[8.0]
|
561
565
|
def change
|
562
566
|
add_column :users, :properties, :json
|
563
567
|
end
|
@@ -569,7 +573,7 @@ end
|
|
569
573
|
Use `jsonb` instead.
|
570
574
|
|
571
575
|
```ruby
|
572
|
-
class AddPropertiesToUsers < ActiveRecord::Migration[
|
576
|
+
class AddPropertiesToUsers < ActiveRecord::Migration[8.0]
|
573
577
|
def change
|
574
578
|
add_column :users, :properties, :jsonb
|
575
579
|
end
|
@@ -585,7 +589,7 @@ end
|
|
585
589
|
In Postgres, setting `NOT NULL` on an existing column blocks reads and writes while every row is checked.
|
586
590
|
|
587
591
|
```ruby
|
588
|
-
class SetSomeColumnNotNull < ActiveRecord::Migration[
|
592
|
+
class SetSomeColumnNotNull < ActiveRecord::Migration[8.0]
|
589
593
|
def change
|
590
594
|
change_column_null :users, :some_column, false
|
591
595
|
end
|
@@ -597,7 +601,7 @@ end
|
|
597
601
|
Instead, add a check constraint.
|
598
602
|
|
599
603
|
```ruby
|
600
|
-
class SetSomeColumnNotNull < ActiveRecord::Migration[
|
604
|
+
class SetSomeColumnNotNull < ActiveRecord::Migration[8.0]
|
601
605
|
def change
|
602
606
|
add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
|
603
607
|
end
|
@@ -607,12 +611,17 @@ end
|
|
607
611
|
Then validate it in a separate migration. Once the check constraint is validated, you can safely set `NOT NULL` on the column and drop the check constraint.
|
608
612
|
|
609
613
|
```ruby
|
610
|
-
class ValidateSomeColumnNotNull < ActiveRecord::Migration[
|
611
|
-
def
|
614
|
+
class ValidateSomeColumnNotNull < ActiveRecord::Migration[8.0]
|
615
|
+
def up
|
612
616
|
validate_check_constraint :users, name: "users_some_column_null"
|
613
617
|
change_column_null :users, :some_column, false
|
614
618
|
remove_check_constraint :users, name: "users_some_column_null"
|
615
619
|
end
|
620
|
+
|
621
|
+
def down
|
622
|
+
add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
|
623
|
+
change_column_null :users, :some_column, true
|
624
|
+
end
|
616
625
|
end
|
617
626
|
```
|
618
627
|
|
@@ -623,7 +632,7 @@ end
|
|
623
632
|
Adding a column with a volatile default value to an existing table causes the entire table to be rewritten. During this time, reads and writes are blocked.
|
624
633
|
|
625
634
|
```ruby
|
626
|
-
class AddSomeColumnToUsers < ActiveRecord::Migration[
|
635
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[8.0]
|
627
636
|
def change
|
628
637
|
add_column :users, :some_column, :uuid, default: "gen_random_uuid()"
|
629
638
|
end
|
@@ -635,7 +644,7 @@ end
|
|
635
644
|
Instead, add the column without a default value, then change the default.
|
636
645
|
|
637
646
|
```ruby
|
638
|
-
class AddSomeColumnToUsers < ActiveRecord::Migration[
|
647
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[8.0]
|
639
648
|
def up
|
640
649
|
add_column :users, :some_column, :uuid
|
641
650
|
change_column_default :users, :some_column, from: nil, to: "gen_random_uuid()"
|
@@ -686,7 +695,7 @@ config.active_record.partial_inserts = false
|
|
686
695
|
Adding a non-unique index with more than three columns rarely improves performance.
|
687
696
|
|
688
697
|
```ruby
|
689
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[
|
698
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[8.0]
|
690
699
|
def change
|
691
700
|
add_index :users, [:a, :b, :c, :d]
|
692
701
|
end
|
@@ -698,7 +707,7 @@ end
|
|
698
707
|
Instead, start an index with columns that narrow down the results the most.
|
699
708
|
|
700
709
|
```ruby
|
701
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[
|
710
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[8.0]
|
702
711
|
def change
|
703
712
|
add_index :users, [:d, :b]
|
704
713
|
end
|
@@ -712,7 +721,7 @@ For Postgres, be sure to add them concurrently.
|
|
712
721
|
To mark a step in the migration as safe, despite using a method that might otherwise be dangerous, wrap it in a `safety_assured` block.
|
713
722
|
|
714
723
|
```ruby
|
715
|
-
class MySafeMigration < ActiveRecord::Migration[
|
724
|
+
class MySafeMigration < ActiveRecord::Migration[8.0]
|
716
725
|
def change
|
717
726
|
safety_assured { remove_column :users, :some_column }
|
718
727
|
end
|
@@ -723,7 +732,7 @@ Certain methods like `execute` and `change_table` cannot be inspected and are pr
|
|
723
732
|
|
724
733
|
## Safe by Default
|
725
734
|
|
726
|
-
Make operations safe by default.
|
735
|
+
Make certain operations safe by default. This allows you to write the code under the "Bad" section, but the migration will be performed as if you had written the "Good" version.
|
727
736
|
|
728
737
|
- adding and removing an index
|
729
738
|
- adding a foreign key
|
@@ -772,6 +781,16 @@ StrongMigrations.disable_check(:add_index)
|
|
772
781
|
|
773
782
|
Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations/error_messages.rb) for the list of keys.
|
774
783
|
|
784
|
+
## Skip Databases
|
785
|
+
|
786
|
+
Skip checks and other functionality for specific databases with:
|
787
|
+
|
788
|
+
```ruby
|
789
|
+
StrongMigrations.skip_database(:catalog)
|
790
|
+
```
|
791
|
+
|
792
|
+
Note: This does not affect `alphabetize_schema`.
|
793
|
+
|
775
794
|
## Down Migrations / Rollbacks
|
776
795
|
|
777
796
|
By default, checks are disabled when migrating down. Enable them with:
|
@@ -853,7 +872,21 @@ production:
|
|
853
872
|
|
854
873
|
For HTTP connections, Redis, and other services, check out [this guide](https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts).
|
855
874
|
|
856
|
-
##
|
875
|
+
## Invalid Indexes
|
876
|
+
|
877
|
+
In Postgres, adding an index non-concurrently can leave behind an invalid index if the lock timeout is reached. Running the migration again can result in an error.
|
878
|
+
|
879
|
+
To automatically remove the invalid index when the migration runs again, use:
|
880
|
+
|
881
|
+
```ruby
|
882
|
+
StrongMigrations.remove_invalid_indexes = true
|
883
|
+
```
|
884
|
+
|
885
|
+
Note: This feature is experimental.
|
886
|
+
|
887
|
+
## Lock Timeout Retries
|
888
|
+
|
889
|
+
Note: This feature is experimental.
|
857
890
|
|
858
891
|
There’s the option to automatically retry statements for migrations when the lock timeout is reached. Here’s how it works:
|
859
892
|
|
@@ -877,7 +910,7 @@ StrongMigrations.lock_timeout_retry_delay = 10.seconds
|
|
877
910
|
To mark migrations as safe that were created before installing this gem, create an initializer with:
|
878
911
|
|
879
912
|
```ruby
|
880
|
-
StrongMigrations.start_after =
|
913
|
+
StrongMigrations.start_after = 20250101000000
|
881
914
|
```
|
882
915
|
|
883
916
|
Use the version from your latest migration.
|
@@ -13,11 +13,11 @@ module StrongMigrations
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def set_statement_timeout(timeout)
|
16
|
-
|
16
|
+
# do nothing
|
17
17
|
end
|
18
18
|
|
19
19
|
def set_lock_timeout(timeout)
|
20
|
-
|
20
|
+
# do nothing
|
21
21
|
end
|
22
22
|
|
23
23
|
def check_lock_timeout(limit)
|
@@ -40,6 +40,9 @@ module StrongMigrations
|
|
40
40
|
["primary_key"]
|
41
41
|
end
|
42
42
|
|
43
|
+
def max_constraint_name_length
|
44
|
+
end
|
45
|
+
|
43
46
|
private
|
44
47
|
|
45
48
|
def connection
|
@@ -127,19 +127,19 @@ module StrongMigrations
|
|
127
127
|
safe
|
128
128
|
end
|
129
129
|
|
130
|
-
|
130
|
+
# TODO remove when Active Record < 7.1 is no longer supported
|
131
|
+
def index_invalid?(table_name, index_name)
|
131
132
|
query = <<~SQL
|
132
133
|
SELECT
|
133
|
-
|
134
|
-
pg_get_constraintdef(oid) AS def
|
134
|
+
indisvalid
|
135
135
|
FROM
|
136
|
-
|
136
|
+
pg_index
|
137
137
|
WHERE
|
138
|
-
|
139
|
-
|
140
|
-
|
138
|
+
indrelid = to_regclass(#{connection.quote(connection.quote_table_name(table_name))}) AND
|
139
|
+
indexrelid = to_regclass(#{connection.quote(connection.quote_table_name(index_name))}) AND
|
140
|
+
indisvalid = false
|
141
141
|
SQL
|
142
|
-
select_all(query.squish).
|
142
|
+
select_all(query.squish).any?
|
143
143
|
end
|
144
144
|
|
145
145
|
def writes_blocked?
|
@@ -173,6 +173,10 @@ module StrongMigrations
|
|
173
173
|
["primary_key", "serial", "bigserial"]
|
174
174
|
end
|
175
175
|
|
176
|
+
def max_constraint_name_length
|
177
|
+
63
|
178
|
+
end
|
179
|
+
|
176
180
|
private
|
177
181
|
|
178
182
|
def set_timeout(setting, timeout)
|
@@ -210,15 +214,9 @@ module StrongMigrations
|
|
210
214
|
end
|
211
215
|
|
212
216
|
def datetime_type
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
# no need to support custom datetime_types
|
217
|
-
connection.class.datetime_type
|
218
|
-
else
|
219
|
-
# https://github.com/rails/rails/issues/21126#issuecomment-327895275
|
220
|
-
:datetime
|
221
|
-
end
|
217
|
+
# https://github.com/rails/rails/pull/41084
|
218
|
+
# no need to support custom datetime_types
|
219
|
+
key = connection.class.datetime_type
|
222
220
|
|
223
221
|
# could be timestamp, timestamp without time zone, timestamp with time zone, etc
|
224
222
|
connection.class.const_get(:NATIVE_DATABASE_TYPES).fetch(key).fetch(:name)
|
@@ -11,10 +11,16 @@ module StrongMigrations
|
|
11
11
|
|
12
12
|
def initialize(migration)
|
13
13
|
@migration = migration
|
14
|
+
reset
|
15
|
+
end
|
16
|
+
|
17
|
+
def reset
|
14
18
|
@new_tables = []
|
15
19
|
@new_columns = []
|
16
20
|
@timeouts_set = false
|
17
21
|
@committed = false
|
22
|
+
@transaction_disabled = false
|
23
|
+
@skip_retries = false
|
18
24
|
end
|
19
25
|
|
20
26
|
def self.safety_assured
|
@@ -27,7 +33,10 @@ module StrongMigrations
|
|
27
33
|
end
|
28
34
|
end
|
29
35
|
|
30
|
-
def perform(method, *args)
|
36
|
+
def perform(method, *args, &block)
|
37
|
+
return yield if skip?
|
38
|
+
|
39
|
+
check_adapter
|
31
40
|
check_version_supported
|
32
41
|
set_timeouts
|
33
42
|
check_lock_timeout
|
@@ -96,9 +105,9 @@ module StrongMigrations
|
|
96
105
|
# TODO figure out how to handle methods that generate multiple statements
|
97
106
|
# like add_reference(table, ref, index: {algorithm: :concurrently})
|
98
107
|
# lock timeout after first statement will cause retry to fail
|
99
|
-
retry_lock_timeouts {
|
108
|
+
retry_lock_timeouts { perform_method(method, *args, &block) }
|
100
109
|
else
|
101
|
-
|
110
|
+
perform_method(method, *args, &block)
|
102
111
|
end
|
103
112
|
|
104
113
|
# outdated statistics + a new index can hurt performance of existing queries
|
@@ -109,6 +118,13 @@ module StrongMigrations
|
|
109
118
|
result
|
110
119
|
end
|
111
120
|
|
121
|
+
def perform_method(method, *args)
|
122
|
+
if StrongMigrations.remove_invalid_indexes && direction == :up && method == :add_index && postgresql?
|
123
|
+
remove_invalid_index_if_needed(*args)
|
124
|
+
end
|
125
|
+
yield
|
126
|
+
end
|
127
|
+
|
112
128
|
def retry_lock_timeouts(check_committed: false)
|
113
129
|
retries = 0
|
114
130
|
begin
|
@@ -129,8 +145,22 @@ module StrongMigrations
|
|
129
145
|
version && version <= StrongMigrations.start_after
|
130
146
|
end
|
131
147
|
|
148
|
+
def skip?
|
149
|
+
StrongMigrations.skipped_databases.map(&:to_s).include?(db_config_name)
|
150
|
+
end
|
151
|
+
|
132
152
|
private
|
133
153
|
|
154
|
+
def check_adapter
|
155
|
+
return if defined?(@adapter_checked)
|
156
|
+
|
157
|
+
if adapter.instance_of?(Adapters::AbstractAdapter)
|
158
|
+
warn "[strong_migrations] Unsupported adapter: #{connection.adapter_name}. Use StrongMigrations.skip_database(#{db_config_name.to_sym.inspect}) to silence this warning."
|
159
|
+
end
|
160
|
+
|
161
|
+
@adapter_checked = true
|
162
|
+
end
|
163
|
+
|
134
164
|
def check_version_supported
|
135
165
|
return if defined?(@version_checked)
|
136
166
|
|
@@ -200,12 +230,48 @@ module StrongMigrations
|
|
200
230
|
@migration.connection
|
201
231
|
end
|
202
232
|
|
233
|
+
def db_config_name
|
234
|
+
connection.pool.db_config.name
|
235
|
+
end
|
236
|
+
|
203
237
|
def retry_lock_timeouts?(method)
|
204
238
|
(
|
205
239
|
StrongMigrations.lock_timeout_retries > 0 &&
|
206
240
|
!in_transaction? &&
|
207
|
-
method != :transaction
|
241
|
+
method != :transaction &&
|
242
|
+
!@skip_retries
|
208
243
|
)
|
209
244
|
end
|
245
|
+
|
246
|
+
def without_retries
|
247
|
+
previous_value = @skip_retries
|
248
|
+
begin
|
249
|
+
@skip_retries = true
|
250
|
+
yield
|
251
|
+
ensure
|
252
|
+
@skip_retries = previous_value
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# REINDEX INDEX CONCURRENTLY leaves a new invalid index if it fails, so use remove_index instead
|
257
|
+
def remove_invalid_index_if_needed(*args)
|
258
|
+
options = args.extract_options!
|
259
|
+
|
260
|
+
# ensures has same options as existing index
|
261
|
+
# check args to avoid errors with index_exists?
|
262
|
+
return unless args.size == 2 && connection.index_exists?(*args, **options.merge(valid: false))
|
263
|
+
|
264
|
+
table, columns = args
|
265
|
+
index_name = options.fetch(:name, connection.index_name(table, columns))
|
266
|
+
|
267
|
+
# valid option is ignored for Active Record < 7.1, so check name as well
|
268
|
+
return if ar_version < 7.1 && !adapter.index_invalid?(table, index_name)
|
269
|
+
|
270
|
+
@migration.say("Attempting to remove invalid index")
|
271
|
+
without_retries do
|
272
|
+
# TODO pass index schema for extra safety?
|
273
|
+
@migration.remove_index(table, **{name: index_name}.merge(options.slice(:algorithm)))
|
274
|
+
end
|
275
|
+
end
|
210
276
|
end
|
211
277
|
end
|
@@ -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] ||
|
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
|
@@ -216,11 +216,11 @@ module StrongMigrations
|
|
216
216
|
table, column, _default_or_changes = args
|
217
217
|
|
218
218
|
# just check ActiveRecord::Base, even though can override on model
|
219
|
-
partial_inserts =
|
219
|
+
partial_inserts = ActiveRecord::Base.partial_inserts
|
220
220
|
|
221
221
|
if partial_inserts && !new_column?(table, column)
|
222
222
|
raise_error :change_column_default,
|
223
|
-
config:
|
223
|
+
config: "partial_inserts"
|
224
224
|
end
|
225
225
|
end
|
226
226
|
|
@@ -228,32 +228,40 @@ module StrongMigrations
|
|
228
228
|
table, column, null, default = args
|
229
229
|
if !null
|
230
230
|
if postgresql?
|
231
|
-
|
231
|
+
constraints = connection.check_constraints(table)
|
232
|
+
safe = constraints.any? { |c| c.options[:validate] && (c.expression == "#{column} IS NOT NULL" || c.expression == "#{connection.quote_column_name(column)} IS NOT NULL") }
|
232
233
|
|
233
234
|
unless safe
|
235
|
+
expression = "#{quote_column_if_needed(column)} IS NOT NULL"
|
236
|
+
|
234
237
|
# match https://github.com/nullobject/rein
|
235
238
|
constraint_name = "#{table}_#{column}_null"
|
239
|
+
if adapter.max_constraint_name_length && constraint_name.bytesize > adapter.max_constraint_name_length
|
240
|
+
constraint_name = connection.check_constraint_options(table, expression, {})[:name]
|
236
241
|
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
+
# avoid collision with Active Record naming for safe_by_default
|
243
|
+
if StrongMigrations.safe_by_default
|
244
|
+
constraint_name = constraint_name.sub("rails", "strong_migrations")
|
245
|
+
end
|
246
|
+
end
|
242
247
|
|
248
|
+
add_args = [table, expression, {name: constraint_name, validate: false}]
|
249
|
+
validate_args = [table, {name: constraint_name}]
|
243
250
|
change_args = [table, column, null]
|
244
|
-
|
245
|
-
validate_constraint_code << "\n #{command_str(:change_column_null, change_args)}"
|
246
|
-
validate_constraint_code << "\n #{command_str(:remove_check_constraint, [table, {name: constraint_name}])}"
|
251
|
+
remove_args = [table, {name: constraint_name}]
|
247
252
|
|
248
253
|
if StrongMigrations.safe_by_default
|
249
|
-
safe_change_column_null(
|
254
|
+
safe_change_column_null(add_args, validate_args, change_args, remove_args, default, constraints)
|
250
255
|
throw :safe
|
251
256
|
end
|
252
257
|
|
253
|
-
add_constraint_code = command_str(:add_check_constraint,
|
258
|
+
add_constraint_code = command_str(:add_check_constraint, add_args)
|
254
259
|
|
260
|
+
up_code = String.new(command_str(:validate_check_constraint, validate_args))
|
261
|
+
up_code << "\n #{command_str(:change_column_null, change_args)}"
|
262
|
+
up_code << "\n #{command_str(:remove_check_constraint, remove_args)}"
|
255
263
|
down_code = "#{add_constraint_code}\n #{command_str(:change_column_null, [table, column, true])}"
|
256
|
-
validate_constraint_code = "def up\n #{
|
264
|
+
validate_constraint_code = "def up\n #{up_code}\n end\n\n def down\n #{down_code}\n end"
|
257
265
|
|
258
266
|
raise_error :change_column_null_postgresql,
|
259
267
|
add_constraint_code: add_constraint_code,
|
@@ -302,29 +310,28 @@ module StrongMigrations
|
|
302
310
|
columns =
|
303
311
|
case method
|
304
312
|
when :remove_timestamps
|
305
|
-
[
|
313
|
+
[:created_at, :updated_at]
|
306
314
|
when :remove_column
|
307
|
-
[args[1]
|
315
|
+
[args[1]]
|
308
316
|
when :remove_columns
|
309
|
-
# Active Record 6.1+ supports options
|
310
317
|
if args.last.is_a?(Hash)
|
311
|
-
args[1..-2]
|
318
|
+
args[1..-2]
|
312
319
|
else
|
313
|
-
args[1..-1]
|
320
|
+
args[1..-1]
|
314
321
|
end
|
315
322
|
else
|
316
323
|
options = args[2] || {}
|
317
324
|
reference = args[1]
|
318
325
|
cols = []
|
319
|
-
cols << "#{reference}_type" if options[:polymorphic]
|
320
|
-
cols << "#{reference}_id"
|
326
|
+
cols << "#{reference}_type".to_sym if options[:polymorphic]
|
327
|
+
cols << "#{reference}_id".to_sym
|
321
328
|
cols
|
322
329
|
end
|
323
330
|
|
324
|
-
code = "self.ignored_columns += #{columns.inspect}"
|
331
|
+
code = "self.ignored_columns += #{columns.map(&:to_s).inspect}"
|
325
332
|
|
326
333
|
raise_error :remove_column,
|
327
|
-
model: args[0]
|
334
|
+
model: model_name(args[0]),
|
328
335
|
code: code,
|
329
336
|
command: command_str(method, args),
|
330
337
|
column_suffix: columns.size > 1 ? "s" : ""
|
@@ -392,7 +399,7 @@ module StrongMigrations
|
|
392
399
|
message = message + append if append
|
393
400
|
|
394
401
|
vars[:migration_name] = @migration.class.name
|
395
|
-
vars[:migration_suffix] =
|
402
|
+
vars[:migration_suffix] = migration_suffix
|
396
403
|
vars[:base_model] = "ApplicationRecord"
|
397
404
|
|
398
405
|
# escape % not followed by {
|
@@ -433,7 +440,7 @@ module StrongMigrations
|
|
433
440
|
end
|
434
441
|
|
435
442
|
def backfill_code(table, column, default, function = false)
|
436
|
-
model = table
|
443
|
+
model = model_name(table)
|
437
444
|
if function
|
438
445
|
# update_all(column: Arel.sql(default)) also works in newer versions of Active Record
|
439
446
|
update_expr = "#{quote_column_if_needed(column)} = #{default}"
|
@@ -456,5 +463,13 @@ module StrongMigrations
|
|
456
463
|
def new_column?(table, column)
|
457
464
|
new_table?(table) || @new_columns.include?([table.to_s, column.to_s])
|
458
465
|
end
|
466
|
+
|
467
|
+
def migration_suffix
|
468
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
469
|
+
end
|
470
|
+
|
471
|
+
def model_name(table)
|
472
|
+
table.to_s.classify
|
473
|
+
end
|
459
474
|
end
|
460
475
|
end
|
@@ -147,7 +147,7 @@ you're doing is safe before proceeding, then wrap it in a safety_assured { ... }
|
|
147
147
|
create_table:
|
148
148
|
"The force option will destroy existing tables.
|
149
149
|
If this is intended, drop the existing table first.
|
150
|
-
|
150
|
+
In any case, remove the force option.",
|
151
151
|
|
152
152
|
execute:
|
153
153
|
"Strong Migrations does not support inspecting what happens inside an
|
@@ -1,8 +1,26 @@
|
|
1
1
|
module StrongMigrations
|
2
|
-
module
|
3
|
-
def
|
2
|
+
module MigrationContext
|
3
|
+
def up(...)
|
4
4
|
super
|
5
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)
|
6
24
|
if e.cause.is_a?(StrongMigrations::Error)
|
7
25
|
# strip cause and clean backtrace
|
8
26
|
def e.cause
|
@@ -6,11 +6,13 @@ module StrongMigrations
|
|
6
6
|
# handle MigrationProxy class
|
7
7
|
migration = migration.send(:migration) if 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.
|
15
|
+
checker.reset
|
14
16
|
|
15
17
|
super(migration, ...)
|
16
18
|
end
|
@@ -4,7 +4,6 @@ module StrongMigrations
|
|
4
4
|
StrongMigrations.safe_by_default && [:add_index, :add_belongs_to, :add_reference, :remove_index, :add_foreign_key, :add_check_constraint, :change_column_null].include?(method)
|
5
5
|
end
|
6
6
|
|
7
|
-
# TODO check if invalid index with expected name exists and remove if needed
|
8
7
|
def safe_add_index(*args, **options)
|
9
8
|
disable_transaction
|
10
9
|
@migration.add_index(*args, **options.merge(algorithm: :concurrently))
|
@@ -47,14 +46,16 @@ module StrongMigrations
|
|
47
46
|
def safe_add_foreign_key(from_table, to_table, *args, **options)
|
48
47
|
@migration.reversible do |dir|
|
49
48
|
dir.up do
|
50
|
-
|
49
|
+
# validate option is unintentionally ignored for Active Record < 7.1
|
50
|
+
# https://github.com/rails/rails/pull/45896
|
51
|
+
if !connection.foreign_key_exists?(from_table, to_table, **options.merge(validate: false))
|
52
|
+
@migration.add_foreign_key(from_table, to_table, *args, **options.merge(validate: false))
|
53
|
+
end
|
51
54
|
disable_transaction
|
52
|
-
|
53
|
-
@migration.validate_foreign_key(from_table, to_table, **validate_options)
|
55
|
+
@migration.validate_foreign_key(from_table, to_table, **options.slice(:column, :name))
|
54
56
|
end
|
55
57
|
dir.down do
|
56
|
-
|
57
|
-
@migration.remove_foreign_key(from_table, to_table, **remove_options)
|
58
|
+
@migration.remove_foreign_key(from_table, to_table, **options.slice(:column, :name))
|
58
59
|
end
|
59
60
|
end
|
60
61
|
end
|
@@ -62,7 +63,10 @@ module StrongMigrations
|
|
62
63
|
def safe_add_check_constraint(table, expression, *args, add_options, validate_options)
|
63
64
|
@migration.reversible do |dir|
|
64
65
|
dir.up do
|
65
|
-
|
66
|
+
# only skip invalid constraints
|
67
|
+
unless connection.check_constraints(table).any? { |c| c.options[:name] == validate_options[:name] && !c.options[:validate] }
|
68
|
+
@migration.add_check_constraint(table, expression, *args, **add_options)
|
69
|
+
end
|
66
70
|
disable_transaction
|
67
71
|
@migration.validate_check_constraint(table, **validate_options)
|
68
72
|
end
|
@@ -72,35 +76,33 @@ module StrongMigrations
|
|
72
76
|
end
|
73
77
|
end
|
74
78
|
|
75
|
-
def safe_change_column_null(
|
79
|
+
def safe_change_column_null(add_args, validate_args, change_args, remove_args, default, constraints)
|
76
80
|
@migration.reversible do |dir|
|
77
81
|
dir.up do
|
78
82
|
unless default.nil?
|
79
83
|
raise Error, "default value not supported yet with safe_by_default"
|
80
84
|
end
|
81
85
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
@migration.
|
89
|
-
@migration.safety_assured do
|
90
|
-
@migration.execute(remove_code)
|
91
|
-
end
|
86
|
+
add_options = add_args.extract_options!
|
87
|
+
validate_options = validate_args.extract_options!
|
88
|
+
remove_options = remove_args.extract_options!
|
89
|
+
|
90
|
+
# only skip invalid constraints
|
91
|
+
unless constraints.any? { |c| c.options[:name] == validate_options[:name] && !c.options[:validate] }
|
92
|
+
@migration.add_check_constraint(*add_args, **add_options)
|
92
93
|
end
|
94
|
+
disable_transaction
|
95
|
+
|
96
|
+
connection.begin_db_transaction
|
97
|
+
@migration.validate_check_constraint(*validate_args, **validate_options)
|
98
|
+
@migration.change_column_null(*change_args)
|
99
|
+
@migration.remove_check_constraint(*remove_args, **remove_options)
|
100
|
+
connection.commit_db_transaction
|
93
101
|
end
|
94
102
|
dir.down do
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
@migration.change_column_null(*down_args)
|
99
|
-
else
|
100
|
-
@migration.safety_assured do
|
101
|
-
@migration.execute(remove_code)
|
102
|
-
end
|
103
|
-
end
|
103
|
+
down_args = change_args.dup
|
104
|
+
down_args[2] = true
|
105
|
+
@migration.change_column_null(*down_args)
|
104
106
|
end
|
105
107
|
end
|
106
108
|
end
|
@@ -109,13 +111,13 @@ module StrongMigrations
|
|
109
111
|
# so just commit at start
|
110
112
|
def disable_transaction
|
111
113
|
if in_transaction? && !transaction_disabled
|
112
|
-
|
114
|
+
connection.commit_db_transaction
|
113
115
|
self.transaction_disabled = true
|
114
116
|
end
|
115
117
|
end
|
116
118
|
|
117
119
|
def in_transaction?
|
118
|
-
|
120
|
+
connection.open_transactions > 0
|
119
121
|
end
|
120
122
|
end
|
121
123
|
end
|
data/lib/strong_migrations.rb
CHANGED
@@ -11,8 +11,8 @@ require_relative "strong_migrations/adapters/postgresql_adapter"
|
|
11
11
|
require_relative "strong_migrations/checks"
|
12
12
|
require_relative "strong_migrations/safe_methods"
|
13
13
|
require_relative "strong_migrations/checker"
|
14
|
-
require_relative "strong_migrations/database_tasks"
|
15
14
|
require_relative "strong_migrations/migration"
|
15
|
+
require_relative "strong_migrations/migration_context"
|
16
16
|
require_relative "strong_migrations/migrator"
|
17
17
|
require_relative "strong_migrations/version"
|
18
18
|
|
@@ -29,7 +29,7 @@ module StrongMigrations
|
|
29
29
|
:target_postgresql_version, :target_mysql_version, :target_mariadb_version,
|
30
30
|
:enabled_checks, :lock_timeout, :statement_timeout, :check_down, :target_version,
|
31
31
|
:safe_by_default, :target_sql_mode, :lock_timeout_retries, :lock_timeout_retry_delay,
|
32
|
-
:alphabetize_schema
|
32
|
+
:alphabetize_schema, :skipped_databases, :remove_invalid_indexes
|
33
33
|
attr_writer :lock_timeout_limit
|
34
34
|
end
|
35
35
|
self.auto_analyze = false
|
@@ -40,6 +40,8 @@ module StrongMigrations
|
|
40
40
|
self.safe_by_default = false
|
41
41
|
self.check_down = false
|
42
42
|
self.alphabetize_schema = false
|
43
|
+
self.skipped_databases = []
|
44
|
+
self.remove_invalid_indexes = false
|
43
45
|
|
44
46
|
# private
|
45
47
|
def self.developer_env?
|
@@ -83,6 +85,10 @@ module StrongMigrations
|
|
83
85
|
false
|
84
86
|
end
|
85
87
|
end
|
88
|
+
|
89
|
+
def self.skip_database(database)
|
90
|
+
self.skipped_databases << database
|
91
|
+
end
|
86
92
|
end
|
87
93
|
|
88
94
|
# load error messages
|
@@ -90,12 +96,9 @@ require_relative "strong_migrations/error_messages"
|
|
90
96
|
|
91
97
|
ActiveSupport.on_load(:active_record) do
|
92
98
|
ActiveRecord::Migration.prepend(StrongMigrations::Migration)
|
99
|
+
ActiveRecord::MigrationContext.prepend(StrongMigrations::MigrationContext)
|
93
100
|
ActiveRecord::Migrator.prepend(StrongMigrations::Migrator)
|
94
101
|
|
95
|
-
if defined?(ActiveRecord::Tasks::DatabaseTasks)
|
96
|
-
ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(StrongMigrations::DatabaseTasks)
|
97
|
-
end
|
98
|
-
|
99
102
|
require_relative "strong_migrations/schema_dumper"
|
100
103
|
ActiveRecord::SchemaDumper.prepend(StrongMigrations::SchemaDumper)
|
101
104
|
end
|
metadata
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strong_migrations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
- Bob Remeika
|
9
9
|
- David Waller
|
10
|
-
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date:
|
12
|
+
date: 2025-02-01 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: activerecord
|
@@ -18,15 +17,14 @@ dependencies:
|
|
18
17
|
requirements:
|
19
18
|
- - ">="
|
20
19
|
- !ruby/object:Gem::Version
|
21
|
-
version: '
|
20
|
+
version: '7'
|
22
21
|
type: :runtime
|
23
22
|
prerelease: false
|
24
23
|
version_requirements: !ruby/object:Gem::Requirement
|
25
24
|
requirements:
|
26
25
|
- - ">="
|
27
26
|
- !ruby/object:Gem::Version
|
28
|
-
version: '
|
29
|
-
description:
|
27
|
+
version: '7'
|
30
28
|
email:
|
31
29
|
- andrew@ankane.org
|
32
30
|
- bob.remeika@gmail.com
|
@@ -47,9 +45,9 @@ files:
|
|
47
45
|
- lib/strong_migrations/adapters/postgresql_adapter.rb
|
48
46
|
- lib/strong_migrations/checker.rb
|
49
47
|
- lib/strong_migrations/checks.rb
|
50
|
-
- lib/strong_migrations/database_tasks.rb
|
51
48
|
- lib/strong_migrations/error_messages.rb
|
52
49
|
- lib/strong_migrations/migration.rb
|
50
|
+
- lib/strong_migrations/migration_context.rb
|
53
51
|
- lib/strong_migrations/migrator.rb
|
54
52
|
- lib/strong_migrations/railtie.rb
|
55
53
|
- lib/strong_migrations/safe_methods.rb
|
@@ -60,7 +58,6 @@ homepage: https://github.com/ankane/strong_migrations
|
|
60
58
|
licenses:
|
61
59
|
- MIT
|
62
60
|
metadata: {}
|
63
|
-
post_install_message:
|
64
61
|
rdoc_options: []
|
65
62
|
require_paths:
|
66
63
|
- lib
|
@@ -75,8 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
75
72
|
- !ruby/object:Gem::Version
|
76
73
|
version: '0'
|
77
74
|
requirements: []
|
78
|
-
rubygems_version: 3.
|
79
|
-
signing_key:
|
75
|
+
rubygems_version: 3.6.2
|
80
76
|
specification_version: 4
|
81
77
|
summary: Catch unsafe migrations in development
|
82
78
|
test_files: []
|