strong_migrations 0.7.6 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -4,18 +4,18 @@ Catch unsafe migrations in development
4
4
 
5
5
  &nbsp;&nbsp;✓&nbsp;&nbsp;Detects potentially dangerous operations<br />&nbsp;&nbsp;✓&nbsp;&nbsp;Prevents them from running by default<br />&nbsp;&nbsp;✓&nbsp;&nbsp;Provides instructions on safer ways to do what you want
6
6
 
7
- Supports for PostgreSQL, MySQL, and MariaDB
7
+ Supports PostgreSQL, MySQL, and MariaDB
8
8
 
9
9
  :tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
10
10
 
11
- [![Build Status](https://github.com/ankane/strong_migrations/workflows/build/badge.svg?branch=master)](https://github.com/ankane/strong_migrations/actions)
11
+ [![Build Status](https://github.com/ankane/strong_migrations/actions/workflows/build.yml/badge.svg)](https://github.com/ankane/strong_migrations/actions)
12
12
 
13
13
  ## Installation
14
14
 
15
15
  Add this line to your application’s Gemfile:
16
16
 
17
17
  ```ruby
18
- gem 'strong_migrations'
18
+ gem "strong_migrations"
19
19
  ```
20
20
 
21
21
  And run:
@@ -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[6.1]
46
+ class RemoveColumn < ActiveRecord::Migration[7.1]
47
47
  def change
48
48
  safety_assured { remove_column :users, :name }
49
49
  end
@@ -62,12 +62,12 @@ Potentially dangerous operations:
62
62
  - [removing a column](#removing-a-column)
63
63
  - [adding a column with a default value](#adding-a-column-with-a-default-value)
64
64
  - [backfilling data](#backfilling-data)
65
+ - [adding a stored generated column](#adding-a-stored-generated-column)
65
66
  - [changing the type of a column](#changing-the-type-of-a-column)
66
67
  - [renaming a column](#renaming-a-column)
67
68
  - [renaming a table](#renaming-a-table)
68
69
  - [creating a table with the force option](#creating-a-table-with-the-force-option)
69
70
  - [adding a check constraint](#adding-a-check-constraint)
70
- - [setting NOT NULL on an existing column](#setting-not-null-on-an-existing-column)
71
71
  - [executing SQL directly](#executing-SQL-directly)
72
72
 
73
73
  Postgres-specific checks:
@@ -75,7 +75,14 @@ Postgres-specific checks:
75
75
  - [adding an index non-concurrently](#adding-an-index-non-concurrently)
76
76
  - [adding a reference](#adding-a-reference)
77
77
  - [adding a foreign key](#adding-a-foreign-key)
78
+ - [adding a unique constraint](#adding-a-unique-constraint)
79
+ - [adding an exclusion constraint](#adding-an-exclusion-constraint)
78
80
  - [adding a json column](#adding-a-json-column)
81
+ - [setting NOT NULL on an existing column](#setting-not-null-on-an-existing-column)
82
+
83
+ Config-specific checks:
84
+
85
+ - [changing the default value of a column](#changing-the-default-value-of-a-column)
79
86
 
80
87
  Best practices:
81
88
 
@@ -90,7 +97,7 @@ You can also add [custom checks](#custom-checks) or [disable specific checks](#d
90
97
  Active Record caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
91
98
 
92
99
  ```ruby
93
- class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.1]
100
+ class RemoveSomeColumnFromUsers < ActiveRecord::Migration[7.1]
94
101
  def change
95
102
  remove_column :users, :some_column
96
103
  end
@@ -107,18 +114,19 @@ end
107
114
  end
108
115
  ```
109
116
 
110
- 2. Deploy code
117
+ 2. Deploy the code
111
118
  3. Write a migration to remove the column (wrap in `safety_assured` block)
112
119
 
113
120
  ```ruby
114
- class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.1]
121
+ class RemoveSomeColumnFromUsers < ActiveRecord::Migration[7.1]
115
122
  def change
116
123
  safety_assured { remove_column :users, :some_column }
117
124
  end
118
125
  end
119
126
  ```
120
127
 
121
- 4. Deploy and run migration
128
+ 4. Deploy and run the migration
129
+ 5. Remove the line added in step 1
122
130
 
123
131
  ### Adding a column with a default value
124
132
 
@@ -127,21 +135,21 @@ end
127
135
  In earlier versions of Postgres, MySQL, and MariaDB, adding a column with a default value to an existing table 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.
128
136
 
129
137
  ```ruby
130
- class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
138
+ class AddSomeColumnToUsers < ActiveRecord::Migration[7.1]
131
139
  def change
132
140
  add_column :users, :some_column, :text, default: "default_value"
133
141
  end
134
142
  end
135
143
  ```
136
144
 
137
- In Postgres 11+, MySQL 8.0.12+, and MariaDB 10.3.2+, this no longer requires a table rewrite and is safe.
145
+ In Postgres 11+, MySQL 8.0.12+, and MariaDB 10.3.2+, this no longer requires a table rewrite and is safe (except for volatile functions like `gen_random_uuid()`).
138
146
 
139
147
  #### Good
140
148
 
141
149
  Instead, add the column without a default value, then change the default.
142
150
 
143
151
  ```ruby
144
- class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
152
+ class AddSomeColumnToUsers < ActiveRecord::Migration[7.1]
145
153
  def up
146
154
  add_column :users, :some_column, :text
147
155
  change_column_default :users, :some_column, "default_value"
@@ -162,7 +170,7 @@ See the next section for how to backfill.
162
170
  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/).
163
171
 
164
172
  ```ruby
165
- class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
173
+ class AddSomeColumnToUsers < ActiveRecord::Migration[7.1]
166
174
  def change
167
175
  add_column :users, :some_column, :text
168
176
  User.update_all some_column: "default_value"
@@ -177,7 +185,7 @@ Also, running a single query to update data can cause issues for large tables.
177
185
  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!`.
178
186
 
179
187
  ```ruby
180
- class BackfillSomeColumn < ActiveRecord::Migration[6.1]
188
+ class BackfillSomeColumn < ActiveRecord::Migration[7.1]
181
189
  disable_ddl_transaction!
182
190
 
183
191
  def up
@@ -189,6 +197,24 @@ class BackfillSomeColumn < ActiveRecord::Migration[6.1]
189
197
  end
190
198
  ```
191
199
 
200
+ ### Adding a stored generated column
201
+
202
+ #### Bad
203
+
204
+ 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.
205
+
206
+ ```ruby
207
+ class AddSomeColumnToUsers < ActiveRecord::Migration[7.1]
208
+ def change
209
+ add_column :users, :some_column, :virtual, type: :string, as: "...", stored: true
210
+ end
211
+ end
212
+ ```
213
+
214
+ #### Good
215
+
216
+ Add a non-generated column and use callbacks or triggers instead (or a virtual generated column with MySQL and MariaDB).
217
+
192
218
  ### Changing the type of a column
193
219
 
194
220
  #### Bad
@@ -196,26 +222,33 @@ end
196
222
  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.
197
223
 
198
224
  ```ruby
199
- class ChangeSomeColumnType < ActiveRecord::Migration[6.1]
225
+ class ChangeSomeColumnType < ActiveRecord::Migration[7.1]
200
226
  def change
201
227
  change_column :users, :some_column, :new_type
202
228
  end
203
229
  end
204
230
  ```
205
231
 
206
- A few changes don’t require a table rewrite (and are safe) in Postgres:
232
+ Some changes don’t require a table rewrite and are safe in Postgres:
207
233
 
208
- - Increasing the length limit of a `varchar` column (or removing the limit)
209
- - Changing a `varchar` column to a `text` column
210
- - Changing a `text` column to a `varchar` column with no length limit
211
- - Increasing the precision of a `decimal` or `numeric` column
212
- - Making a `decimal` or `numeric` column unconstrained
213
- - Changing between `timestamp` and `timestamptz` columns when session time zone is UTC in Postgres 12+
234
+ Type | Safe Changes
235
+ --- | ---
236
+ `cidr` | Changing to `inet`
237
+ `citext` | Changing to `text` if not indexed, changing to `string` with no `:limit` if not indexed
238
+ `datetime` | Increasing or removing `:precision`, changing to `timestamptz` when session time zone is UTC in Postgres 12+
239
+ `decimal` | Increasing `:precision` at same `:scale`, removing `:precision` and `:scale`
240
+ `interval` | Increasing or removing `:precision`
241
+ `numeric` | Increasing `:precision` at same `:scale`, removing `:precision` and `:scale`
242
+ `string` | Increasing or removing `:limit`, changing to `text`, changing `citext` if not indexed
243
+ `text` | Changing to `string` with no `:limit`, changing to `citext` if not indexed
244
+ `time` | Increasing or removing `:precision`
245
+ `timestamptz` | Increasing or removing `:limit`, changing to `datetime` when session time zone is UTC in Postgres 12+
214
246
 
215
- And a few in MySQL and MariaDB:
247
+ And some in MySQL and MariaDB:
216
248
 
217
- - Increasing the length limit of a `varchar` column from under 255 up to 255
218
- - Increasing the length limit of a `varchar` column from over 255 to the max limit
249
+ Type | Safe Changes
250
+ --- | ---
251
+ `string` | Increasing `:limit` from under 63 up to 63, increasing `:limit` from over 63 to the max (the threshold can be different if using an encoding other than `utf8mb4` - for instance, it’s 85 for `utf8mb3` and 255 for `latin1`)
219
252
 
220
253
  #### Good
221
254
 
@@ -235,7 +268,7 @@ A safer approach is to:
235
268
  Renaming a column that’s in use will cause errors in your application.
236
269
 
237
270
  ```ruby
238
- class RenameSomeColumn < ActiveRecord::Migration[6.1]
271
+ class RenameSomeColumn < ActiveRecord::Migration[7.1]
239
272
  def change
240
273
  rename_column :users, :some_column, :new_name
241
274
  end
@@ -260,7 +293,7 @@ A safer approach is to:
260
293
  Renaming a table that’s in use will cause errors in your application.
261
294
 
262
295
  ```ruby
263
- class RenameUsersToCustomers < ActiveRecord::Migration[6.1]
296
+ class RenameUsersToCustomers < ActiveRecord::Migration[7.1]
264
297
  def change
265
298
  rename_table :users, :customers
266
299
  end
@@ -273,7 +306,7 @@ A safer approach is to:
273
306
 
274
307
  1. Create a new table
275
308
  2. Write to both tables
276
- 3. Backfill data from the old table to new table
309
+ 3. Backfill data from the old table to the new table
277
310
  4. Move reads from the old table to the new table
278
311
  5. Stop writing to the old table
279
312
  6. Drop the old table
@@ -285,7 +318,7 @@ A safer approach is to:
285
318
  The `force` option can drop an existing table.
286
319
 
287
320
  ```ruby
288
- class CreateUsers < ActiveRecord::Migration[6.1]
321
+ class CreateUsers < ActiveRecord::Migration[7.1]
289
322
  def change
290
323
  create_table :users, force: true do |t|
291
324
  # ...
@@ -299,7 +332,7 @@ end
299
332
  Create tables without the `force` option.
300
333
 
301
334
  ```ruby
302
- class CreateUsers < ActiveRecord::Migration[6.1]
335
+ class CreateUsers < ActiveRecord::Migration[7.1]
303
336
  def change
304
337
  create_table :users do |t|
305
338
  # ...
@@ -319,7 +352,7 @@ If you intend to drop an existing table, run `drop_table` first.
319
352
  Adding a check constraint blocks reads and writes in Postgres and blocks writes in MySQL and MariaDB while every row is checked.
320
353
 
321
354
  ```ruby
322
- class AddCheckConstraint < ActiveRecord::Migration[6.1]
355
+ class AddCheckConstraint < ActiveRecord::Migration[7.1]
323
356
  def change
324
357
  add_check_constraint :users, "price > 0", name: "price_check"
325
358
  end
@@ -331,7 +364,7 @@ end
331
364
  Add the check constraint without validating existing rows:
332
365
 
333
366
  ```ruby
334
- class AddCheckConstraint < ActiveRecord::Migration[6.1]
367
+ class AddCheckConstraint < ActiveRecord::Migration[7.1]
335
368
  def change
336
369
  add_check_constraint :users, "price > 0", name: "price_check", validate: false
337
370
  end
@@ -341,7 +374,7 @@ end
341
374
  Then validate them in a separate migration.
342
375
 
343
376
  ```ruby
344
- class ValidateCheckConstraint < ActiveRecord::Migration[6.1]
377
+ class ValidateCheckConstraint < ActiveRecord::Migration[7.1]
345
378
  def change
346
379
  validate_check_constraint :users, name: "price_check"
347
380
  end
@@ -352,264 +385,315 @@ end
352
385
 
353
386
  [Let us know](https://github.com/ankane/strong_migrations/issues/new) if you have a safe way to do this (check constraints can be added with `NOT ENFORCED`, but enforcing blocks writes).
354
387
 
355
- ### Setting NOT NULL on an existing column
388
+ ### Executing SQL directly
389
+
390
+ Strong Migrations can’t ensure safety for raw SQL statements. Make really sure that what you’re doing is safe, then use:
391
+
392
+ ```ruby
393
+ class ExecuteSQL < ActiveRecord::Migration[7.1]
394
+ def change
395
+ safety_assured { execute "..." }
396
+ end
397
+ end
398
+ ```
399
+
400
+ ### Adding an index non-concurrently
356
401
 
357
402
  :turtle: Safe by default available
358
403
 
359
404
  #### Bad
360
405
 
361
- Setting `NOT NULL` on an existing column blocks reads and writes while every row is checked.
406
+ In Postgres, adding an index non-concurrently blocks writes.
362
407
 
363
408
  ```ruby
364
- class SetSomeColumnNotNull < ActiveRecord::Migration[6.1]
409
+ class AddSomeIndexToUsers < ActiveRecord::Migration[7.1]
365
410
  def change
366
- change_column_null :users, :some_column, false
411
+ add_index :users, :some_column
367
412
  end
368
413
  end
369
414
  ```
370
415
 
371
- #### Good - Postgres
372
-
373
- Instead, add a check constraint.
416
+ #### Good
374
417
 
375
- For Rails 6.1, use:
418
+ Add indexes concurrently.
376
419
 
377
420
  ```ruby
378
- class SetSomeColumnNotNull < ActiveRecord::Migration[6.1]
421
+ class AddSomeIndexToUsers < ActiveRecord::Migration[7.1]
422
+ disable_ddl_transaction!
423
+
379
424
  def change
380
- add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
425
+ add_index :users, :some_column, algorithm: :concurrently
381
426
  end
382
427
  end
383
428
  ```
384
429
 
385
- For Rails < 6.1, use:
430
+ If you forget `disable_ddl_transaction!`, the migration will fail. Also, note that indexes on new tables (those created in the same migration) don’t require this.
431
+
432
+ With [gindex](https://github.com/ankane/gindex), you can generate an index migration instantly with:
433
+
434
+ ```sh
435
+ rails g index table column
436
+ ```
437
+
438
+ ### Adding a reference
439
+
440
+ :turtle: Safe by default available
441
+
442
+ #### Bad
443
+
444
+ Rails adds an index non-concurrently to references by default, which blocks writes in Postgres.
386
445
 
387
446
  ```ruby
388
- class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
447
+ class AddReferenceToUsers < ActiveRecord::Migration[7.1]
389
448
  def change
390
- safety_assured do
391
- execute 'ALTER TABLE "users" ADD CONSTRAINT "users_some_column_null" CHECK ("some_column" IS NOT NULL) NOT VALID'
392
- end
449
+ add_reference :users, :city
393
450
  end
394
451
  end
395
452
  ```
396
453
 
397
- Then validate it in a separate migration. A `NOT NULL` check constraint is [functionally equivalent](https://medium.com/doctolib/adding-a-not-null-constraint-on-pg-faster-with-minimal-locking-38b2c00c4d1c) to setting `NOT NULL` on the column (but it won’t show up in `schema.rb` in Rails < 6.1). In Postgres 12+, once the check constraint is validated, you can safely set `NOT NULL` on the column and drop the check constraint.
454
+ #### Good
398
455
 
399
- For Rails 6.1, use:
456
+ Make sure the index is added concurrently.
400
457
 
401
458
  ```ruby
402
- class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.1]
403
- def change
404
- validate_check_constraint :users, name: "users_some_column_null"
459
+ class AddReferenceToUsers < ActiveRecord::Migration[7.1]
460
+ disable_ddl_transaction!
405
461
 
406
- # in Postgres 12+, you can then safely set NOT NULL on the column
407
- change_column_null :users, :some_column, false
408
- remove_check_constraint :users, name: "users_some_column_null"
462
+ def change
463
+ add_reference :users, :city, index: {algorithm: :concurrently}
409
464
  end
410
465
  end
411
466
  ```
412
467
 
413
- For Rails < 6.1, use:
468
+ ### Adding a foreign key
469
+
470
+ :turtle: Safe by default available
471
+
472
+ #### Bad
473
+
474
+ In Postgres, adding a foreign key blocks writes on both tables.
414
475
 
415
476
  ```ruby
416
- class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
477
+ class AddForeignKeyOnUsers < ActiveRecord::Migration[7.1]
417
478
  def change
418
- safety_assured do
419
- execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "users_some_column_null"'
420
- end
421
-
422
- # in Postgres 12+, you can then safely set NOT NULL on the column
423
- change_column_null :users, :some_column, false
424
- safety_assured do
425
- execute 'ALTER TABLE "users" DROP CONSTRAINT "users_some_column_null"'
426
- end
479
+ add_foreign_key :users, :orders
427
480
  end
428
481
  end
429
482
  ```
430
483
 
431
- #### Good - MySQL and MariaDB
484
+ or
432
485
 
433
- [Let us know](https://github.com/ankane/strong_migrations/issues/new) if you have a safe way to do this.
486
+ ```ruby
487
+ class AddReferenceToUsers < ActiveRecord::Migration[7.1]
488
+ def change
489
+ add_reference :users, :order, foreign_key: true
490
+ end
491
+ end
492
+ ```
434
493
 
435
- ### Executing SQL directly
494
+ #### Good
436
495
 
437
- Strong Migrations can’t ensure safety for raw SQL statements. Make really sure that what you’re doing is safe, then use:
496
+ Add the foreign key without validating existing rows:
438
497
 
439
498
  ```ruby
440
- class ExecuteSQL < ActiveRecord::Migration[6.1]
499
+ class AddForeignKeyOnUsers < ActiveRecord::Migration[7.1]
441
500
  def change
442
- safety_assured { execute "..." }
501
+ add_foreign_key :users, :orders, validate: false
443
502
  end
444
503
  end
445
504
  ```
446
505
 
447
- ### Adding an index non-concurrently
506
+ Then validate them in a separate migration.
448
507
 
449
- :turtle: Safe by default available
508
+ ```ruby
509
+ class ValidateForeignKeyOnUsers < ActiveRecord::Migration[7.1]
510
+ def change
511
+ validate_foreign_key :users, :orders
512
+ end
513
+ end
514
+ ```
515
+
516
+ ### Adding a unique constraint
450
517
 
451
518
  #### Bad
452
519
 
453
- In Postgres, adding an index non-concurrently blocks writes.
520
+ In Postgres, adding a unique constraint creates a unique index, which blocks reads and writes.
454
521
 
455
522
  ```ruby
456
- class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
523
+ class AddUniqueContraint < ActiveRecord::Migration[7.1]
457
524
  def change
458
- add_index :users, :some_column
525
+ add_unique_constraint :users, :some_column
459
526
  end
460
527
  end
461
528
  ```
462
529
 
463
530
  #### Good
464
531
 
465
- Add indexes concurrently.
532
+ Create a unique index concurrently, then use it for the constraint.
466
533
 
467
534
  ```ruby
468
- class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
535
+ class AddUniqueContraint < ActiveRecord::Migration[7.1]
469
536
  disable_ddl_transaction!
470
537
 
471
- def change
472
- add_index :users, :some_column, algorithm: :concurrently
538
+ def up
539
+ add_index :users, :some_column, unique: true, algorithm: :concurrently
540
+ add_unique_constraint :users, using_index: "index_users_on_some_column"
541
+ end
542
+
543
+ def down
544
+ remove_unique_constraint :users, :some_column
473
545
  end
474
546
  end
475
547
  ```
476
548
 
477
- If you forget `disable_ddl_transaction!`, the migration will fail. Also, note that indexes on new tables (those created in the same migration) don’t require this.
549
+ ### Adding an exclusion constraint
478
550
 
479
- With [gindex](https://github.com/ankane/gindex), you can generate an index migration instantly with:
551
+ #### Bad
480
552
 
481
- ```sh
482
- rails g index table column
553
+ In Postgres, adding an exclusion constraint blocks reads and writes while every row is checked.
554
+
555
+ ```ruby
556
+ class AddExclusionContraint < ActiveRecord::Migration[7.1]
557
+ def change
558
+ add_exclusion_constraint :users, "number WITH =", using: :gist
559
+ end
560
+ end
483
561
  ```
484
562
 
485
- ### Adding a reference
563
+ #### Good
486
564
 
487
- :turtle: Safe by default available
565
+ [Let us know](https://github.com/ankane/strong_migrations/issues/new) if you have a safe way to do this (exclusion constraints cannot be marked `NOT VALID`).
566
+
567
+ ### Adding a json column
488
568
 
489
569
  #### Bad
490
570
 
491
- Rails adds an index non-concurrently to references by default, which blocks writes in Postgres.
571
+ In Postgres, there’s no equality operator for the `json` column type, which can cause errors for existing `SELECT DISTINCT` queries in your application.
492
572
 
493
573
  ```ruby
494
- class AddReferenceToUsers < ActiveRecord::Migration[6.1]
574
+ class AddPropertiesToUsers < ActiveRecord::Migration[7.1]
495
575
  def change
496
- add_reference :users, :city
576
+ add_column :users, :properties, :json
497
577
  end
498
578
  end
499
579
  ```
500
580
 
501
581
  #### Good
502
582
 
503
- Make sure the index is added concurrently.
583
+ Use `jsonb` instead.
504
584
 
505
585
  ```ruby
506
- class AddReferenceToUsers < ActiveRecord::Migration[6.1]
507
- disable_ddl_transaction!
508
-
586
+ class AddPropertiesToUsers < ActiveRecord::Migration[7.1]
509
587
  def change
510
- add_reference :users, :city, index: {algorithm: :concurrently}
588
+ add_column :users, :properties, :jsonb
511
589
  end
512
590
  end
513
591
  ```
514
592
 
515
- ### Adding a foreign key
593
+ ### Setting NOT NULL on an existing column
516
594
 
517
595
  :turtle: Safe by default available
518
596
 
519
597
  #### Bad
520
598
 
521
- In Postgres, adding a foreign key blocks writes on both tables.
599
+ In Postgres, setting `NOT NULL` on an existing column blocks reads and writes while every row is checked.
522
600
 
523
601
  ```ruby
524
- class AddForeignKeyOnUsers < ActiveRecord::Migration[6.1]
602
+ class SetSomeColumnNotNull < ActiveRecord::Migration[7.1]
525
603
  def change
526
- add_foreign_key :users, :orders
527
- end
528
- end
529
- ```
530
-
531
- or
532
-
533
- ```ruby
534
- class AddReferenceToUsers < ActiveRecord::Migration[6.1]
535
- def change
536
- add_reference :users, :order, foreign_key: true
604
+ change_column_null :users, :some_column, false
537
605
  end
538
606
  end
539
607
  ```
540
608
 
541
609
  #### Good
542
610
 
543
- Add the foreign key without validating existing rows, then validate them in a separate migration.
611
+ Instead, add a check constraint.
544
612
 
545
- For Rails 5.2+, use:
613
+ For Rails 6.1+, use:
546
614
 
547
615
  ```ruby
548
- class AddForeignKeyOnUsers < ActiveRecord::Migration[6.1]
616
+ class SetSomeColumnNotNull < ActiveRecord::Migration[7.1]
549
617
  def change
550
- add_foreign_key :users, :orders, validate: false
618
+ add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
551
619
  end
552
620
  end
553
621
  ```
554
622
 
555
- Then:
623
+ For Rails < 6.1, use:
556
624
 
557
625
  ```ruby
558
- class ValidateForeignKeyOnUsers < ActiveRecord::Migration[6.1]
626
+ class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
559
627
  def change
560
- validate_foreign_key :users, :orders
628
+ safety_assured do
629
+ execute 'ALTER TABLE "users" ADD CONSTRAINT "users_some_column_null" CHECK ("some_column" IS NOT NULL) NOT VALID'
630
+ end
561
631
  end
562
632
  end
563
633
  ```
564
634
 
565
- For Rails < 5.2, use:
635
+ Then validate it in a separate migration. A `NOT NULL` check constraint is [functionally equivalent](https://medium.com/doctolib/adding-a-not-null-constraint-on-pg-faster-with-minimal-locking-38b2c00c4d1c) to setting `NOT NULL` on the column (but it won’t show up in `schema.rb` in Rails < 6.1). In Postgres 12+, once the check constraint is validated, you can safely set `NOT NULL` on the column and drop the check constraint.
636
+
637
+ For Rails 6.1+, use:
566
638
 
567
639
  ```ruby
568
- class AddForeignKeyOnUsers < ActiveRecord::Migration[5.1]
640
+ class ValidateSomeColumnNotNull < ActiveRecord::Migration[7.1]
569
641
  def change
570
- safety_assured do
571
- execute 'ALTER TABLE "users" ADD CONSTRAINT "fk_rails_c1e9b98e31" FOREIGN KEY ("order_id") REFERENCES "orders" ("id") NOT VALID'
572
- end
642
+ validate_check_constraint :users, name: "users_some_column_null"
643
+
644
+ # in Postgres 12+, you can then safely set NOT NULL on the column
645
+ change_column_null :users, :some_column, false
646
+ remove_check_constraint :users, name: "users_some_column_null"
573
647
  end
574
648
  end
575
649
  ```
576
650
 
577
- Then:
651
+ For Rails < 6.1, use:
578
652
 
579
653
  ```ruby
580
- class ValidateForeignKeyOnUsers < ActiveRecord::Migration[5.1]
654
+ class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
581
655
  def change
582
656
  safety_assured do
583
- execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "fk_rails_c1e9b98e31"'
657
+ execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "users_some_column_null"'
658
+ end
659
+
660
+ # in Postgres 12+, you can then safely set NOT NULL on the column
661
+ change_column_null :users, :some_column, false
662
+ safety_assured do
663
+ execute 'ALTER TABLE "users" DROP CONSTRAINT "users_some_column_null"'
584
664
  end
585
665
  end
586
666
  end
587
667
  ```
588
668
 
589
- ### Adding a json column
669
+ ### Changing the default value of a column
590
670
 
591
671
  #### Bad
592
672
 
593
- In Postgres, there’s no equality operator for the `json` column type, which can cause errors for existing `SELECT DISTINCT` queries in your application.
673
+ Rails < 7 enables partial writes by default, which can cause incorrect values to be inserted when changing the default value of a column.
594
674
 
595
675
  ```ruby
596
- class AddPropertiesToUsers < ActiveRecord::Migration[6.1]
676
+ class ChangeSomeColumnDefault < ActiveRecord::Migration[6.1]
597
677
  def change
598
- add_column :users, :properties, :json
678
+ change_column_default :users, :some_column, from: "old", to: "new"
599
679
  end
600
680
  end
681
+
682
+ User.create!(some_column: "old") # can insert "new"
601
683
  ```
602
684
 
603
685
  #### Good
604
686
 
605
- Use `jsonb` instead.
687
+ Disable partial writes in `config/application.rb`. For Rails < 7, use:
606
688
 
607
689
  ```ruby
608
- class AddPropertiesToUsers < ActiveRecord::Migration[6.1]
609
- def change
610
- add_column :users, :properties, :jsonb
611
- end
612
- end
690
+ config.active_record.partial_writes = false
691
+ ```
692
+
693
+ For Rails 7+, use:
694
+
695
+ ```ruby
696
+ config.active_record.partial_inserts = false
613
697
  ```
614
698
 
615
699
  ### Keeping non-unique indexes to three columns or less
@@ -619,7 +703,7 @@ end
619
703
  Adding a non-unique index with more than three columns rarely improves performance.
620
704
 
621
705
  ```ruby
622
- class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
706
+ class AddSomeIndexToUsers < ActiveRecord::Migration[7.1]
623
707
  def change
624
708
  add_index :users, [:a, :b, :c, :d]
625
709
  end
@@ -631,7 +715,7 @@ end
631
715
  Instead, start an index with columns that narrow down the results the most.
632
716
 
633
717
  ```ruby
634
- class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
718
+ class AddSomeIndexToUsers < ActiveRecord::Migration[7.1]
635
719
  def change
636
720
  add_index :users, [:b, :d]
637
721
  end
@@ -645,7 +729,7 @@ For Postgres, be sure to add them concurrently.
645
729
  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.
646
730
 
647
731
  ```ruby
648
- class MySafeMigration < ActiveRecord::Migration[6.1]
732
+ class MySafeMigration < ActiveRecord::Migration[7.1]
649
733
  def change
650
734
  safety_assured { remove_column :users, :some_column }
651
735
  end
@@ -703,7 +787,7 @@ Disable specific checks with:
703
787
  StrongMigrations.disable_check(:add_index)
704
788
  ```
705
789
 
706
- Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations.rb) for the list of keys.
790
+ Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations/error_messages.rb) for the list of keys.
707
791
 
708
792
  ## Down Migrations / Rollbacks
709
793
 
@@ -721,7 +805,7 @@ To customize specific messages, create an initializer with:
721
805
  StrongMigrations.error_messages[:add_column_default] = "Your custom instructions"
722
806
  ```
723
807
 
724
- Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations.rb) for the list of keys.
808
+ Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations/error_messages.rb) for the list of keys.
725
809
 
726
810
  ## Migration Timeouts
727
811
 
@@ -786,12 +870,31 @@ production:
786
870
 
787
871
  For HTTP connections, Redis, and other services, check out [this guide](https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts).
788
872
 
873
+ ## Lock Timeout Retries [experimental]
874
+
875
+ There’s the option to automatically retry statements for migrations when the lock timeout is reached. Here’s how it works:
876
+
877
+ - If a lock timeout happens outside a transaction, the statement is retried
878
+ - If it happens inside the DDL transaction, the entire migration is retried (only applicable to Postgres)
879
+
880
+ Add to `config/initializers/strong_migrations.rb`:
881
+
882
+ ```ruby
883
+ StrongMigrations.lock_timeout_retries = 3
884
+ ```
885
+
886
+ Set the delay between retries with:
887
+
888
+ ```ruby
889
+ StrongMigrations.lock_timeout_retry_delay = 10.seconds
890
+ ```
891
+
789
892
  ## Existing Migrations
790
893
 
791
894
  To mark migrations as safe that were created before installing this gem, create an initializer with:
792
895
 
793
896
  ```ruby
794
- StrongMigrations.start_after = 20170101000000
897
+ StrongMigrations.start_after = 20230101000000
795
898
  ```
796
899
 
797
900
  Use the version from your latest migration.
@@ -808,6 +911,12 @@ The major version works well for Postgres, while the full version is recommended
808
911
 
809
912
  For safety, this option only affects development and test environments. In other environments, the actual server version is always used.
810
913
 
914
+ If your app has multiple databases with different versions, with Rails 6.1+, you can use:
915
+
916
+ ```ruby
917
+ StrongMigrations.target_version = {primary: 13, catalog: 15}
918
+ ```
919
+
811
920
  ## Analyze Tables
812
921
 
813
922
  Analyze tables automatically (to update planner statistics) after an index is added. Create an initializer with:
@@ -818,19 +927,18 @@ StrongMigrations.auto_analyze = true
818
927
 
819
928
  ## Faster Migrations
820
929
 
821
- Only dump the schema when adding a new migration. If you use Git, create an initializer with:
930
+ Only dump the schema when adding a new migration. If you use Git, add to `config/environments/development.rb`:
822
931
 
823
- ```ruby
824
- ActiveRecord::Base.dump_schema_after_migration = Rails.env.development? &&
825
- `git status db/migrate/ --porcelain`.present?
932
+ ```rb
933
+ config.active_record.dump_schema_after_migration = `git status db/migrate/ --porcelain`.present?
826
934
  ```
827
935
 
828
936
  ## Schema Sanity
829
937
 
830
- Columns can flip order in `db/schema.rb` when you have multiple developers. One way to prevent this is to [alphabetize them](https://www.pgrs.net/2008/03/12/alphabetize-schema-rb-columns/). Add to the end of your `Rakefile`:
938
+ Columns can flip order in `db/schema.rb` when you have multiple developers. One way to prevent this is to [alphabetize them](https://www.pgrs.net/2008/03/12/alphabetize-schema-rb-columns/). Add to `config/initializers/strong_migrations.rb`:
831
939
 
832
940
  ```ruby
833
- task "db:schema:dump": "strong_migrations:alphabetize_columns"
941
+ StrongMigrations.alphabetize_schema = true
834
942
  ```
835
943
 
836
944
  ## Permissions