strong_migrations 2.0.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -0
- data/LICENSE.txt +1 -1
- data/README.md +105 -45
- data/lib/generators/strong_migrations/templates/initializer.rb.tt +3 -0
- data/lib/strong_migrations/adapters/abstract_adapter.rb +9 -2
- data/lib/strong_migrations/adapters/mysql_adapter.rb +4 -0
- data/lib/strong_migrations/adapters/postgresql_adapter.rb +17 -24
- data/lib/strong_migrations/checker.rb +85 -5
- data/lib/strong_migrations/checks.rb +73 -28
- data/lib/strong_migrations/error_messages.rb +28 -1
- data/lib/strong_migrations/migration.rb +3 -5
- data/lib/strong_migrations/{database_tasks.rb → migration_context.rb} +20 -3
- data/lib/strong_migrations/migrator.rb +7 -5
- data/lib/strong_migrations/safe_methods.rb +57 -30
- data/lib/strong_migrations/schema_dumper.rb +15 -4
- data/lib/strong_migrations/version.rb +1 -1
- data/lib/strong_migrations.rb +9 -6
- metadata +7 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 971d5d1eabb91dc04c72cc99a24f8b92f1b0d31d8d28369577f48c283b8d8624
|
4
|
+
data.tar.gz: '08a413578f564ada7f9768aeaf469719ca051e4c37057be6324e593c73fb3144'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b8ca27268e166609ec8253ce686c6fe9cbe6d7c4b298f8c80ce7f1690e76a2c997fad2806cce52c9765f6ee92e7e389fdf6c830d3ca8ebb0241574adc02c4b57
|
7
|
+
data.tar.gz: 18731d2b1bdd12698d6b860a470249a24ea12aaa0b5b74b6510d41527ef2126c46b27ef4aa7c7adb1437bd7a006d0bb7c46490d24ec15720009723e5b0d8eca4
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,45 @@
|
|
1
|
+
## 2.5.0 (2025-07-27)
|
2
|
+
|
3
|
+
- Added check for `rename_schema`
|
4
|
+
- Made `auto_analyze` apply to `add_reference`
|
5
|
+
|
6
|
+
## 2.4.0 (2025-06-23)
|
7
|
+
|
8
|
+
- Added `remove_invalid_indexes` option to install generator
|
9
|
+
- Added experimental `transaction_timeout` option
|
10
|
+
- Dropped support for Ruby < 3.2 and Active Record < 7.1
|
11
|
+
|
12
|
+
## 2.3.0 (2025-04-03)
|
13
|
+
|
14
|
+
- Added check for `change_column` for columns with check constraints with Postgres
|
15
|
+
|
16
|
+
## 2.2.1 (2025-03-21)
|
17
|
+
|
18
|
+
- Added support for `change_column_null` with default value with `safe_by_default` option
|
19
|
+
- Improved backfill instructions
|
20
|
+
- Fixed `safe_by_default` applying to migrations before `start_after`
|
21
|
+
|
22
|
+
## 2.2.0 (2025-02-01)
|
23
|
+
|
24
|
+
- Fixed constraint name for long table and column names with `change_column_null`
|
25
|
+
- Dropped support for Active Record < 7
|
26
|
+
|
27
|
+
## 2.1.0 (2024-11-08)
|
28
|
+
|
29
|
+
- Added `skip_database` method
|
30
|
+
- Added experimental `remove_invalid_indexes` option
|
31
|
+
- Added warning for unsupported adapters
|
32
|
+
- Improved output for `db:forward`, `db:rollback`, `db:migrate:up`, and `db:migrate:down`
|
33
|
+
- Made operations more retriable with `safe_by_default`
|
34
|
+
|
35
|
+
## 2.0.2 (2024-10-30)
|
36
|
+
|
37
|
+
- Fixed migrations not running with Active Record 8 rc2
|
38
|
+
|
39
|
+
## 2.0.1 (2024-10-14)
|
40
|
+
|
41
|
+
- Fixed issue with `alphabetize_schema` and virtual columns
|
42
|
+
|
1
43
|
## 2.0.0 (2024-06-28)
|
2
44
|
|
3
45
|
- Improved install generator for Trilogy
|
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
|
@@ -80,6 +80,7 @@ Postgres-specific checks:
|
|
80
80
|
- [adding a json column](#adding-a-json-column)
|
81
81
|
- [setting NOT NULL on an existing column](#setting-not-null-on-an-existing-column)
|
82
82
|
- [adding a column with a volatile default value](#adding-a-column-with-a-volatile-default-value)
|
83
|
+
- [renaming a schema](#renaming-a-schema)
|
83
84
|
|
84
85
|
Config-specific checks:
|
85
86
|
|
@@ -98,7 +99,7 @@ You can also add [custom checks](#custom-checks) or [disable specific checks](#d
|
|
98
99
|
Active Record caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
|
99
100
|
|
100
101
|
```ruby
|
101
|
-
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[
|
102
|
+
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[8.0]
|
102
103
|
def change
|
103
104
|
remove_column :users, :some_column
|
104
105
|
end
|
@@ -119,7 +120,7 @@ end
|
|
119
120
|
3. Write a migration to remove the column (wrap in `safety_assured` block)
|
120
121
|
|
121
122
|
```ruby
|
122
|
-
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[
|
123
|
+
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[8.0]
|
123
124
|
def change
|
124
125
|
safety_assured { remove_column :users, :some_column }
|
125
126
|
end
|
@@ -136,7 +137,7 @@ end
|
|
136
137
|
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
138
|
|
138
139
|
```ruby
|
139
|
-
class ChangeSomeColumnType < ActiveRecord::Migration[
|
140
|
+
class ChangeSomeColumnType < ActiveRecord::Migration[8.0]
|
140
141
|
def change
|
141
142
|
change_column :users, :some_column, :new_type
|
142
143
|
end
|
@@ -182,7 +183,7 @@ A safer approach is to:
|
|
182
183
|
Renaming a column that’s in use will cause errors in your application.
|
183
184
|
|
184
185
|
```ruby
|
185
|
-
class RenameSomeColumn < ActiveRecord::Migration[
|
186
|
+
class RenameSomeColumn < ActiveRecord::Migration[8.0]
|
186
187
|
def change
|
187
188
|
rename_column :users, :some_column, :new_name
|
188
189
|
end
|
@@ -207,7 +208,7 @@ A safer approach is to:
|
|
207
208
|
Renaming a table that’s in use will cause errors in your application.
|
208
209
|
|
209
210
|
```ruby
|
210
|
-
class RenameUsersToCustomers < ActiveRecord::Migration[
|
211
|
+
class RenameUsersToCustomers < ActiveRecord::Migration[8.0]
|
211
212
|
def change
|
212
213
|
rename_table :users, :customers
|
213
214
|
end
|
@@ -232,7 +233,7 @@ A safer approach is to:
|
|
232
233
|
The `force` option can drop an existing table.
|
233
234
|
|
234
235
|
```ruby
|
235
|
-
class CreateUsers < ActiveRecord::Migration[
|
236
|
+
class CreateUsers < ActiveRecord::Migration[8.0]
|
236
237
|
def change
|
237
238
|
create_table :users, force: true do |t|
|
238
239
|
# ...
|
@@ -246,7 +247,7 @@ end
|
|
246
247
|
Create tables without the `force` option.
|
247
248
|
|
248
249
|
```ruby
|
249
|
-
class CreateUsers < ActiveRecord::Migration[
|
250
|
+
class CreateUsers < ActiveRecord::Migration[8.0]
|
250
251
|
def change
|
251
252
|
create_table :users do |t|
|
252
253
|
# ...
|
@@ -264,7 +265,7 @@ If you intend to drop an existing table, run `drop_table` first.
|
|
264
265
|
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
266
|
|
266
267
|
```ruby
|
267
|
-
class AddIdToCitiesUsers < ActiveRecord::Migration[
|
268
|
+
class AddIdToCitiesUsers < ActiveRecord::Migration[8.0]
|
268
269
|
def change
|
269
270
|
add_column :cities_users, :id, :primary_key
|
270
271
|
end
|
@@ -284,7 +285,7 @@ Create a new table and migrate the data with the same steps as [renaming a table
|
|
284
285
|
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
286
|
|
286
287
|
```ruby
|
287
|
-
class AddSomeColumnToUsers < ActiveRecord::Migration[
|
288
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[8.0]
|
288
289
|
def change
|
289
290
|
add_column :users, :some_column, :virtual, type: :string, as: "...", stored: true
|
290
291
|
end
|
@@ -304,7 +305,7 @@ Add a non-generated column and use callbacks or triggers instead (or a virtual g
|
|
304
305
|
Adding a check constraint blocks reads and writes in Postgres and blocks writes in MySQL and MariaDB while every row is checked.
|
305
306
|
|
306
307
|
```ruby
|
307
|
-
class AddCheckConstraint < ActiveRecord::Migration[
|
308
|
+
class AddCheckConstraint < ActiveRecord::Migration[8.0]
|
308
309
|
def change
|
309
310
|
add_check_constraint :users, "price > 0", name: "price_check"
|
310
311
|
end
|
@@ -316,7 +317,7 @@ end
|
|
316
317
|
Add the check constraint without validating existing rows:
|
317
318
|
|
318
319
|
```ruby
|
319
|
-
class AddCheckConstraint < ActiveRecord::Migration[
|
320
|
+
class AddCheckConstraint < ActiveRecord::Migration[8.0]
|
320
321
|
def change
|
321
322
|
add_check_constraint :users, "price > 0", name: "price_check", validate: false
|
322
323
|
end
|
@@ -326,7 +327,7 @@ end
|
|
326
327
|
Then validate them in a separate migration.
|
327
328
|
|
328
329
|
```ruby
|
329
|
-
class ValidateCheckConstraint < ActiveRecord::Migration[
|
330
|
+
class ValidateCheckConstraint < ActiveRecord::Migration[8.0]
|
330
331
|
def change
|
331
332
|
validate_check_constraint :users, name: "price_check"
|
332
333
|
end
|
@@ -342,7 +343,7 @@ end
|
|
342
343
|
Strong Migrations can’t ensure safety for raw SQL statements. Make really sure that what you’re doing is safe, then use:
|
343
344
|
|
344
345
|
```ruby
|
345
|
-
class ExecuteSQL < ActiveRecord::Migration[
|
346
|
+
class ExecuteSQL < ActiveRecord::Migration[8.0]
|
346
347
|
def change
|
347
348
|
safety_assured { execute "..." }
|
348
349
|
end
|
@@ -351,12 +352,14 @@ end
|
|
351
352
|
|
352
353
|
### Backfilling data
|
353
354
|
|
355
|
+
Note: Strong Migrations does not detect dangerous backfills.
|
356
|
+
|
354
357
|
#### Bad
|
355
358
|
|
356
359
|
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
360
|
|
358
361
|
```ruby
|
359
|
-
class AddSomeColumnToUsers < ActiveRecord::Migration[
|
362
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[8.0]
|
360
363
|
def change
|
361
364
|
add_column :users, :some_column, :text
|
362
365
|
User.update_all some_column: "default_value"
|
@@ -371,18 +374,20 @@ Also, running a single query to update data can cause issues for large tables.
|
|
371
374
|
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
375
|
|
373
376
|
```ruby
|
374
|
-
class BackfillSomeColumn < ActiveRecord::Migration[
|
377
|
+
class BackfillSomeColumn < ActiveRecord::Migration[8.0]
|
375
378
|
disable_ddl_transaction!
|
376
379
|
|
377
380
|
def up
|
378
|
-
User.unscoped.in_batches do |relation|
|
379
|
-
relation.update_all some_column: "default_value"
|
381
|
+
User.unscoped.in_batches(of: 10000) do |relation|
|
382
|
+
relation.where(some_column: nil).update_all some_column: "default_value"
|
380
383
|
sleep(0.01) # throttle
|
381
384
|
end
|
382
385
|
end
|
383
386
|
end
|
384
387
|
```
|
385
388
|
|
389
|
+
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.
|
390
|
+
|
386
391
|
### Adding an index non-concurrently
|
387
392
|
|
388
393
|
:turtle: Safe by default available
|
@@ -392,7 +397,7 @@ end
|
|
392
397
|
In Postgres, adding an index non-concurrently blocks writes.
|
393
398
|
|
394
399
|
```ruby
|
395
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[
|
400
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[8.0]
|
396
401
|
def change
|
397
402
|
add_index :users, :some_column
|
398
403
|
end
|
@@ -404,7 +409,7 @@ end
|
|
404
409
|
Add indexes concurrently.
|
405
410
|
|
406
411
|
```ruby
|
407
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[
|
412
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[8.0]
|
408
413
|
disable_ddl_transaction!
|
409
414
|
|
410
415
|
def change
|
@@ -430,7 +435,7 @@ rails g index table column
|
|
430
435
|
Rails adds an index non-concurrently to references by default, which blocks writes in Postgres.
|
431
436
|
|
432
437
|
```ruby
|
433
|
-
class AddReferenceToUsers < ActiveRecord::Migration[
|
438
|
+
class AddReferenceToUsers < ActiveRecord::Migration[8.0]
|
434
439
|
def change
|
435
440
|
add_reference :users, :city
|
436
441
|
end
|
@@ -442,7 +447,7 @@ end
|
|
442
447
|
Make sure the index is added concurrently.
|
443
448
|
|
444
449
|
```ruby
|
445
|
-
class AddReferenceToUsers < ActiveRecord::Migration[
|
450
|
+
class AddReferenceToUsers < ActiveRecord::Migration[8.0]
|
446
451
|
disable_ddl_transaction!
|
447
452
|
|
448
453
|
def change
|
@@ -460,7 +465,7 @@ end
|
|
460
465
|
In Postgres, adding a foreign key blocks writes on both tables.
|
461
466
|
|
462
467
|
```ruby
|
463
|
-
class AddForeignKeyOnUsers < ActiveRecord::Migration[
|
468
|
+
class AddForeignKeyOnUsers < ActiveRecord::Migration[8.0]
|
464
469
|
def change
|
465
470
|
add_foreign_key :users, :orders
|
466
471
|
end
|
@@ -470,7 +475,7 @@ end
|
|
470
475
|
or
|
471
476
|
|
472
477
|
```ruby
|
473
|
-
class AddReferenceToUsers < ActiveRecord::Migration[
|
478
|
+
class AddReferenceToUsers < ActiveRecord::Migration[8.0]
|
474
479
|
def change
|
475
480
|
add_reference :users, :order, foreign_key: true
|
476
481
|
end
|
@@ -482,7 +487,7 @@ end
|
|
482
487
|
Add the foreign key without validating existing rows:
|
483
488
|
|
484
489
|
```ruby
|
485
|
-
class AddForeignKeyOnUsers < ActiveRecord::Migration[
|
490
|
+
class AddForeignKeyOnUsers < ActiveRecord::Migration[8.0]
|
486
491
|
def change
|
487
492
|
add_foreign_key :users, :orders, validate: false
|
488
493
|
end
|
@@ -492,7 +497,7 @@ end
|
|
492
497
|
Then validate them in a separate migration.
|
493
498
|
|
494
499
|
```ruby
|
495
|
-
class ValidateForeignKeyOnUsers < ActiveRecord::Migration[
|
500
|
+
class ValidateForeignKeyOnUsers < ActiveRecord::Migration[8.0]
|
496
501
|
def change
|
497
502
|
validate_foreign_key :users, :orders
|
498
503
|
end
|
@@ -506,7 +511,7 @@ end
|
|
506
511
|
In Postgres, adding a unique constraint creates a unique index, which blocks reads and writes.
|
507
512
|
|
508
513
|
```ruby
|
509
|
-
class AddUniqueConstraint < ActiveRecord::Migration[
|
514
|
+
class AddUniqueConstraint < ActiveRecord::Migration[8.0]
|
510
515
|
def change
|
511
516
|
add_unique_constraint :users, :some_column
|
512
517
|
end
|
@@ -518,7 +523,7 @@ end
|
|
518
523
|
Create a unique index concurrently, then use it for the constraint.
|
519
524
|
|
520
525
|
```ruby
|
521
|
-
class AddUniqueConstraint < ActiveRecord::Migration[
|
526
|
+
class AddUniqueConstraint < ActiveRecord::Migration[8.0]
|
522
527
|
disable_ddl_transaction!
|
523
528
|
|
524
529
|
def up
|
@@ -539,7 +544,7 @@ end
|
|
539
544
|
In Postgres, adding an exclusion constraint blocks reads and writes while every row is checked.
|
540
545
|
|
541
546
|
```ruby
|
542
|
-
class AddExclusionConstraint < ActiveRecord::Migration[
|
547
|
+
class AddExclusionConstraint < ActiveRecord::Migration[8.0]
|
543
548
|
def change
|
544
549
|
add_exclusion_constraint :users, "number WITH =", using: :gist
|
545
550
|
end
|
@@ -557,7 +562,7 @@ end
|
|
557
562
|
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
563
|
|
559
564
|
```ruby
|
560
|
-
class AddPropertiesToUsers < ActiveRecord::Migration[
|
565
|
+
class AddPropertiesToUsers < ActiveRecord::Migration[8.0]
|
561
566
|
def change
|
562
567
|
add_column :users, :properties, :json
|
563
568
|
end
|
@@ -569,7 +574,7 @@ end
|
|
569
574
|
Use `jsonb` instead.
|
570
575
|
|
571
576
|
```ruby
|
572
|
-
class AddPropertiesToUsers < ActiveRecord::Migration[
|
577
|
+
class AddPropertiesToUsers < ActiveRecord::Migration[8.0]
|
573
578
|
def change
|
574
579
|
add_column :users, :properties, :jsonb
|
575
580
|
end
|
@@ -585,7 +590,7 @@ end
|
|
585
590
|
In Postgres, setting `NOT NULL` on an existing column blocks reads and writes while every row is checked.
|
586
591
|
|
587
592
|
```ruby
|
588
|
-
class SetSomeColumnNotNull < ActiveRecord::Migration[
|
593
|
+
class SetSomeColumnNotNull < ActiveRecord::Migration[8.0]
|
589
594
|
def change
|
590
595
|
change_column_null :users, :some_column, false
|
591
596
|
end
|
@@ -597,7 +602,7 @@ end
|
|
597
602
|
Instead, add a check constraint.
|
598
603
|
|
599
604
|
```ruby
|
600
|
-
class SetSomeColumnNotNull < ActiveRecord::Migration[
|
605
|
+
class SetSomeColumnNotNull < ActiveRecord::Migration[8.0]
|
601
606
|
def change
|
602
607
|
add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
|
603
608
|
end
|
@@ -607,12 +612,17 @@ end
|
|
607
612
|
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
613
|
|
609
614
|
```ruby
|
610
|
-
class ValidateSomeColumnNotNull < ActiveRecord::Migration[
|
611
|
-
def
|
615
|
+
class ValidateSomeColumnNotNull < ActiveRecord::Migration[8.0]
|
616
|
+
def up
|
612
617
|
validate_check_constraint :users, name: "users_some_column_null"
|
613
618
|
change_column_null :users, :some_column, false
|
614
619
|
remove_check_constraint :users, name: "users_some_column_null"
|
615
620
|
end
|
621
|
+
|
622
|
+
def down
|
623
|
+
add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
|
624
|
+
change_column_null :users, :some_column, true
|
625
|
+
end
|
616
626
|
end
|
617
627
|
```
|
618
628
|
|
@@ -623,7 +633,7 @@ end
|
|
623
633
|
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
634
|
|
625
635
|
```ruby
|
626
|
-
class AddSomeColumnToUsers < ActiveRecord::Migration[
|
636
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[8.0]
|
627
637
|
def change
|
628
638
|
add_column :users, :some_column, :uuid, default: "gen_random_uuid()"
|
629
639
|
end
|
@@ -635,7 +645,7 @@ end
|
|
635
645
|
Instead, add the column without a default value, then change the default.
|
636
646
|
|
637
647
|
```ruby
|
638
|
-
class AddSomeColumnToUsers < ActiveRecord::Migration[
|
648
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[8.0]
|
639
649
|
def up
|
640
650
|
add_column :users, :some_column, :uuid
|
641
651
|
change_column_default :users, :some_column, from: nil, to: "gen_random_uuid()"
|
@@ -649,6 +659,31 @@ end
|
|
649
659
|
|
650
660
|
Then [backfill the data](#backfilling-data).
|
651
661
|
|
662
|
+
### Renaming a schema
|
663
|
+
|
664
|
+
#### Bad
|
665
|
+
|
666
|
+
Renaming a schema that’s in use will cause errors in your application.
|
667
|
+
|
668
|
+
```ruby
|
669
|
+
class RenameUsersToCustomers < ActiveRecord::Migration[8.1]
|
670
|
+
def change
|
671
|
+
rename_schema :users, :customers
|
672
|
+
end
|
673
|
+
end
|
674
|
+
```
|
675
|
+
|
676
|
+
#### Good
|
677
|
+
|
678
|
+
A safer approach is to:
|
679
|
+
|
680
|
+
1. Create a new schema
|
681
|
+
2. Write to both schemas
|
682
|
+
3. Backfill data from the old schema to the new schema
|
683
|
+
4. Move reads from the old schema to the new schema
|
684
|
+
5. Stop writing to the old schema
|
685
|
+
6. Drop the old schema
|
686
|
+
|
652
687
|
### Changing the default value of a column
|
653
688
|
|
654
689
|
#### Bad
|
@@ -686,7 +721,7 @@ config.active_record.partial_inserts = false
|
|
686
721
|
Adding a non-unique index with more than three columns rarely improves performance.
|
687
722
|
|
688
723
|
```ruby
|
689
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[
|
724
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[8.0]
|
690
725
|
def change
|
691
726
|
add_index :users, [:a, :b, :c, :d]
|
692
727
|
end
|
@@ -698,7 +733,7 @@ end
|
|
698
733
|
Instead, start an index with columns that narrow down the results the most.
|
699
734
|
|
700
735
|
```ruby
|
701
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[
|
736
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[8.0]
|
702
737
|
def change
|
703
738
|
add_index :users, [:d, :b]
|
704
739
|
end
|
@@ -712,7 +747,7 @@ For Postgres, be sure to add them concurrently.
|
|
712
747
|
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
748
|
|
714
749
|
```ruby
|
715
|
-
class MySafeMigration < ActiveRecord::Migration[
|
750
|
+
class MySafeMigration < ActiveRecord::Migration[8.0]
|
716
751
|
def change
|
717
752
|
safety_assured { remove_column :users, :some_column }
|
718
753
|
end
|
@@ -723,7 +758,7 @@ Certain methods like `execute` and `change_table` cannot be inspected and are pr
|
|
723
758
|
|
724
759
|
## Safe by Default
|
725
760
|
|
726
|
-
Make operations safe by default.
|
761
|
+
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
762
|
|
728
763
|
- adding and removing an index
|
729
764
|
- adding a foreign key
|
@@ -772,6 +807,16 @@ StrongMigrations.disable_check(:add_index)
|
|
772
807
|
|
773
808
|
Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations/error_messages.rb) for the list of keys.
|
774
809
|
|
810
|
+
## Skip Databases
|
811
|
+
|
812
|
+
Skip checks and other functionality for specific databases with:
|
813
|
+
|
814
|
+
```ruby
|
815
|
+
StrongMigrations.skip_database(:catalog)
|
816
|
+
```
|
817
|
+
|
818
|
+
Note: This does not affect `alphabetize_schema`.
|
819
|
+
|
775
820
|
## Down Migrations / Rollbacks
|
776
821
|
|
777
822
|
By default, checks are disabled when migrating down. Enable them with:
|
@@ -853,7 +898,19 @@ production:
|
|
853
898
|
|
854
899
|
For HTTP connections, Redis, and other services, check out [this guide](https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts).
|
855
900
|
|
856
|
-
##
|
901
|
+
## Invalid Indexes
|
902
|
+
|
903
|
+
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.
|
904
|
+
|
905
|
+
To automatically remove the invalid index when the migration runs again, use:
|
906
|
+
|
907
|
+
```ruby
|
908
|
+
StrongMigrations.remove_invalid_indexes = true
|
909
|
+
```
|
910
|
+
|
911
|
+
## Lock Timeout Retries
|
912
|
+
|
913
|
+
Note: This feature is experimental.
|
857
914
|
|
858
915
|
There’s the option to automatically retry statements for migrations when the lock timeout is reached. Here’s how it works:
|
859
916
|
|
@@ -877,7 +934,7 @@ StrongMigrations.lock_timeout_retry_delay = 10.seconds
|
|
877
934
|
To mark migrations as safe that were created before installing this gem, create an initializer with:
|
878
935
|
|
879
936
|
```ruby
|
880
|
-
StrongMigrations.start_after =
|
937
|
+
StrongMigrations.start_after = 20250101000000
|
881
938
|
```
|
882
939
|
|
883
940
|
Use the version from your latest migration.
|
@@ -934,15 +991,18 @@ You probably don’t need this gem for smaller projects, as operations that are
|
|
934
991
|
|
935
992
|
## Additional Reading
|
936
993
|
|
937
|
-
- [Rails Migrations with No Downtime](https://pedro.herokuapp.com/past/2011/7/13/rails_migrations_with_no_downtime/)
|
938
994
|
- [PostgreSQL at Scale: Database Schema Changes Without Downtime](https://medium.com/braintree-product-technology/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680)
|
939
|
-
- [
|
995
|
+
- [MySQL InnoDB Online DDL Operations](https://dev.mysql.com/doc/refman/en/innodb-online-ddl-operations.html)
|
940
996
|
- [MariaDB InnoDB Online DDL Overview](https://mariadb.com/kb/en/innodb-online-ddl-overview/)
|
941
997
|
|
942
998
|
## Credits
|
943
999
|
|
944
1000
|
Thanks to Bob Remeika and David Waller for the [original code](https://github.com/foobarfighter/safe-migrations) and [Sean Huber](https://github.com/LendingHome/zero_downtime_migrations) for the bad/good readme format.
|
945
1001
|
|
1002
|
+
## History
|
1003
|
+
|
1004
|
+
View the [changelog](https://github.com/ankane/strong_migrations/blob/master/CHANGELOG.md)
|
1005
|
+
|
946
1006
|
## Contributing
|
947
1007
|
|
948
1008
|
Everyone is encouraged to help improve this project. Here are a few ways you can help:
|
@@ -20,6 +20,9 @@ StrongMigrations.auto_analyze = true
|
|
20
20
|
# end
|
21
21
|
# end<% if postgresql? %>
|
22
22
|
|
23
|
+
# Remove invalid indexes when rerunning migrations
|
24
|
+
# StrongMigrations.remove_invalid_indexes = true
|
25
|
+
|
23
26
|
# Make some operations safe by default
|
24
27
|
# See https://github.com/ankane/strong_migrations#safe-by-default
|
25
28
|
# StrongMigrations.safe_by_default = true<% end %>
|
@@ -13,11 +13,15 @@ module StrongMigrations
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def set_statement_timeout(timeout)
|
16
|
-
|
16
|
+
# do nothing
|
17
|
+
end
|
18
|
+
|
19
|
+
def set_transaction_timeout(timeout)
|
20
|
+
# do nothing
|
17
21
|
end
|
18
22
|
|
19
23
|
def set_lock_timeout(timeout)
|
20
|
-
|
24
|
+
# do nothing
|
21
25
|
end
|
22
26
|
|
23
27
|
def check_lock_timeout(limit)
|
@@ -40,6 +44,9 @@ module StrongMigrations
|
|
40
44
|
["primary_key"]
|
41
45
|
end
|
42
46
|
|
47
|
+
def max_constraint_name_length
|
48
|
+
end
|
49
|
+
|
43
50
|
private
|
44
51
|
|
45
52
|
def connection
|
@@ -23,6 +23,11 @@ module StrongMigrations
|
|
23
23
|
set_timeout("statement_timeout", timeout)
|
24
24
|
end
|
25
25
|
|
26
|
+
def set_transaction_timeout(timeout)
|
27
|
+
# TODO make sure true version supports it as well?
|
28
|
+
set_timeout("transaction_timeout", timeout) if server_version >= Gem::Version.new("17")
|
29
|
+
end
|
30
|
+
|
26
31
|
def set_lock_timeout(timeout)
|
27
32
|
set_timeout("lock_timeout", timeout)
|
28
33
|
end
|
@@ -127,21 +132,6 @@ module StrongMigrations
|
|
127
132
|
safe
|
128
133
|
end
|
129
134
|
|
130
|
-
def constraints(table_name)
|
131
|
-
query = <<~SQL
|
132
|
-
SELECT
|
133
|
-
conname AS name,
|
134
|
-
pg_get_constraintdef(oid) AS def
|
135
|
-
FROM
|
136
|
-
pg_constraint
|
137
|
-
WHERE
|
138
|
-
contype = 'c' AND
|
139
|
-
convalidated AND
|
140
|
-
conrelid = #{connection.quote(connection.quote_table_name(table_name))}::regclass
|
141
|
-
SQL
|
142
|
-
select_all(query.squish).to_a
|
143
|
-
end
|
144
|
-
|
145
135
|
def writes_blocked?
|
146
136
|
query = <<~SQL
|
147
137
|
SELECT
|
@@ -173,6 +163,15 @@ module StrongMigrations
|
|
173
163
|
["primary_key", "serial", "bigserial"]
|
174
164
|
end
|
175
165
|
|
166
|
+
def max_constraint_name_length
|
167
|
+
63
|
168
|
+
end
|
169
|
+
|
170
|
+
def constraints(table, column)
|
171
|
+
# TODO improve column check
|
172
|
+
connection.check_constraints(table).select { |c| /\b#{Regexp.escape(column.to_s)}\b/.match?(c.expression) }
|
173
|
+
end
|
174
|
+
|
176
175
|
private
|
177
176
|
|
178
177
|
def set_timeout(setting, timeout)
|
@@ -210,15 +209,9 @@ module StrongMigrations
|
|
210
209
|
end
|
211
210
|
|
212
211
|
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
|
212
|
+
# https://github.com/rails/rails/pull/41084
|
213
|
+
# no need to support custom datetime_types
|
214
|
+
key = connection.class.datetime_type
|
222
215
|
|
223
216
|
# could be timestamp, timestamp without time zone, timestamp with time zone, etc
|
224
217
|
connection.class.const_get(:NATIVE_DATABASE_TYPES).fetch(key).fetch(:name)
|