strong_migrations 1.6.3 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd18b6da9358b37dfe07adbda611a65905173b4c4bd1aae1920588002d89afe6
4
- data.tar.gz: 87f5393be68a9c685adbb38a29ac1980615f933f28b1b03634c2618af3db3963
3
+ metadata.gz: 0b621e800497f9e9c77e0208a899f7e8d17a944caa61dffdea80911e8ddd5ead
4
+ data.tar.gz: 3f7d4275c416db6b954c1c2e1109ad5118a1c0a7cc6eb25a81203f82a4e47b35
5
5
  SHA512:
6
- metadata.gz: 4bde3a8af5e9bae18dcf270155afeefb82b726d93c9b49ac7eeee72706af2c85a31401b8eb415b89f2df3c3234ec74b1c5b5fd48404da0b0cdb65d1c5d205099
7
- data.tar.gz: 352f30e395b489175ef038d6bb418266a2ecdb7fb3f7b0df4dd1d5290994efae1962b87b8dc0e4a5a494718a781e7552ef9f530001880f493f497ac374beb6c9
6
+ metadata.gz: '0243834c1f5b12bc637a00b49f8b7eb773dcdd5b8592a354b705d72b6e5b6a728babfb12fc3d058093086529ebfe7de34abd9f1c834bc95323b01f8a48417445'
7
+ data.tar.gz: a24deb56f0adbe0206791b9155707489d03c02f021842f9284eaadcf5a2b99c30fe6f3ae2c51fef15fc5fef9bf7066b1a72f84c446ed8c12919c43f29eee9f95
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 1.7.0 (2024-01-05)
2
+
3
+ - Added check for `add_unique_constraint`
4
+
5
+ ## 1.6.4 (2023-10-17)
6
+
7
+ - Fixed false positives with `revert`
8
+
1
9
  ## 1.6.3 (2023-09-20)
2
10
 
3
11
  - Added support for Trilogy
@@ -241,7 +249,7 @@ Other
241
249
  ## 0.2.2 (2018-02-14)
242
250
 
243
251
  - Friendlier output
244
- - Better method of hooking into ActiveRecord
252
+ - Better method of hooking into Active Record
245
253
 
246
254
  ## 0.2.1 (2018-02-07)
247
255
 
data/README.md CHANGED
@@ -8,7 +8,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
 
@@ -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[7.0]
46
+ class RemoveColumn < ActiveRecord::Migration[7.1]
47
47
  def change
48
48
  safety_assured { remove_column :users, :name }
49
49
  end
@@ -75,6 +75,7 @@ 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)
78
79
  - [adding an exclusion constraint](#adding-an-exclusion-constraint)
79
80
  - [adding a json column](#adding-a-json-column)
80
81
  - [setting NOT NULL on an existing column](#setting-not-null-on-an-existing-column)
@@ -96,7 +97,7 @@ You can also add [custom checks](#custom-checks) or [disable specific checks](#d
96
97
  Active Record caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
97
98
 
98
99
  ```ruby
99
- class RemoveSomeColumnFromUsers < ActiveRecord::Migration[7.0]
100
+ class RemoveSomeColumnFromUsers < ActiveRecord::Migration[7.1]
100
101
  def change
101
102
  remove_column :users, :some_column
102
103
  end
@@ -117,7 +118,7 @@ end
117
118
  3. Write a migration to remove the column (wrap in `safety_assured` block)
118
119
 
119
120
  ```ruby
120
- class RemoveSomeColumnFromUsers < ActiveRecord::Migration[7.0]
121
+ class RemoveSomeColumnFromUsers < ActiveRecord::Migration[7.1]
121
122
  def change
122
123
  safety_assured { remove_column :users, :some_column }
123
124
  end
@@ -134,7 +135,7 @@ end
134
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.
135
136
 
136
137
  ```ruby
137
- class AddSomeColumnToUsers < ActiveRecord::Migration[7.0]
138
+ class AddSomeColumnToUsers < ActiveRecord::Migration[7.1]
138
139
  def change
139
140
  add_column :users, :some_column, :text, default: "default_value"
140
141
  end
@@ -148,7 +149,7 @@ In Postgres 11+, MySQL 8.0.12+, and MariaDB 10.3.2+, this no longer requires a t
148
149
  Instead, add the column without a default value, then change the default.
149
150
 
150
151
  ```ruby
151
- class AddSomeColumnToUsers < ActiveRecord::Migration[7.0]
152
+ class AddSomeColumnToUsers < ActiveRecord::Migration[7.1]
152
153
  def up
153
154
  add_column :users, :some_column, :text
154
155
  change_column_default :users, :some_column, "default_value"
@@ -169,7 +170,7 @@ See the next section for how to backfill.
169
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/).
170
171
 
171
172
  ```ruby
172
- class AddSomeColumnToUsers < ActiveRecord::Migration[7.0]
173
+ class AddSomeColumnToUsers < ActiveRecord::Migration[7.1]
173
174
  def change
174
175
  add_column :users, :some_column, :text
175
176
  User.update_all some_column: "default_value"
@@ -184,7 +185,7 @@ Also, running a single query to update data can cause issues for large tables.
184
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!`.
185
186
 
186
187
  ```ruby
187
- class BackfillSomeColumn < ActiveRecord::Migration[7.0]
188
+ class BackfillSomeColumn < ActiveRecord::Migration[7.1]
188
189
  disable_ddl_transaction!
189
190
 
190
191
  def up
@@ -203,7 +204,7 @@ end
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.
204
205
 
205
206
  ```ruby
206
- class AddSomeColumnToUsers < ActiveRecord::Migration[7.0]
207
+ class AddSomeColumnToUsers < ActiveRecord::Migration[7.1]
207
208
  def change
208
209
  add_column :users, :some_column, :virtual, type: :string, as: "...", stored: true
209
210
  end
@@ -221,7 +222,7 @@ Add a non-generated column and use callbacks or triggers instead (or a virtual g
221
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.
222
223
 
223
224
  ```ruby
224
- class ChangeSomeColumnType < ActiveRecord::Migration[7.0]
225
+ class ChangeSomeColumnType < ActiveRecord::Migration[7.1]
225
226
  def change
226
227
  change_column :users, :some_column, :new_type
227
228
  end
@@ -267,7 +268,7 @@ A safer approach is to:
267
268
  Renaming a column that’s in use will cause errors in your application.
268
269
 
269
270
  ```ruby
270
- class RenameSomeColumn < ActiveRecord::Migration[7.0]
271
+ class RenameSomeColumn < ActiveRecord::Migration[7.1]
271
272
  def change
272
273
  rename_column :users, :some_column, :new_name
273
274
  end
@@ -292,7 +293,7 @@ A safer approach is to:
292
293
  Renaming a table that’s in use will cause errors in your application.
293
294
 
294
295
  ```ruby
295
- class RenameUsersToCustomers < ActiveRecord::Migration[7.0]
296
+ class RenameUsersToCustomers < ActiveRecord::Migration[7.1]
296
297
  def change
297
298
  rename_table :users, :customers
298
299
  end
@@ -305,7 +306,7 @@ A safer approach is to:
305
306
 
306
307
  1. Create a new table
307
308
  2. Write to both tables
308
- 3. Backfill data from the old table to new table
309
+ 3. Backfill data from the old table to the new table
309
310
  4. Move reads from the old table to the new table
310
311
  5. Stop writing to the old table
311
312
  6. Drop the old table
@@ -317,7 +318,7 @@ A safer approach is to:
317
318
  The `force` option can drop an existing table.
318
319
 
319
320
  ```ruby
320
- class CreateUsers < ActiveRecord::Migration[7.0]
321
+ class CreateUsers < ActiveRecord::Migration[7.1]
321
322
  def change
322
323
  create_table :users, force: true do |t|
323
324
  # ...
@@ -331,7 +332,7 @@ end
331
332
  Create tables without the `force` option.
332
333
 
333
334
  ```ruby
334
- class CreateUsers < ActiveRecord::Migration[7.0]
335
+ class CreateUsers < ActiveRecord::Migration[7.1]
335
336
  def change
336
337
  create_table :users do |t|
337
338
  # ...
@@ -351,7 +352,7 @@ If you intend to drop an existing table, run `drop_table` first.
351
352
  Adding a check constraint blocks reads and writes in Postgres and blocks writes in MySQL and MariaDB while every row is checked.
352
353
 
353
354
  ```ruby
354
- class AddCheckConstraint < ActiveRecord::Migration[7.0]
355
+ class AddCheckConstraint < ActiveRecord::Migration[7.1]
355
356
  def change
356
357
  add_check_constraint :users, "price > 0", name: "price_check"
357
358
  end
@@ -363,7 +364,7 @@ end
363
364
  Add the check constraint without validating existing rows:
364
365
 
365
366
  ```ruby
366
- class AddCheckConstraint < ActiveRecord::Migration[7.0]
367
+ class AddCheckConstraint < ActiveRecord::Migration[7.1]
367
368
  def change
368
369
  add_check_constraint :users, "price > 0", name: "price_check", validate: false
369
370
  end
@@ -373,7 +374,7 @@ end
373
374
  Then validate them in a separate migration.
374
375
 
375
376
  ```ruby
376
- class ValidateCheckConstraint < ActiveRecord::Migration[7.0]
377
+ class ValidateCheckConstraint < ActiveRecord::Migration[7.1]
377
378
  def change
378
379
  validate_check_constraint :users, name: "price_check"
379
380
  end
@@ -389,7 +390,7 @@ end
389
390
  Strong Migrations can’t ensure safety for raw SQL statements. Make really sure that what you’re doing is safe, then use:
390
391
 
391
392
  ```ruby
392
- class ExecuteSQL < ActiveRecord::Migration[7.0]
393
+ class ExecuteSQL < ActiveRecord::Migration[7.1]
393
394
  def change
394
395
  safety_assured { execute "..." }
395
396
  end
@@ -405,7 +406,7 @@ end
405
406
  In Postgres, adding an index non-concurrently blocks writes.
406
407
 
407
408
  ```ruby
408
- class AddSomeIndexToUsers < ActiveRecord::Migration[7.0]
409
+ class AddSomeIndexToUsers < ActiveRecord::Migration[7.1]
409
410
  def change
410
411
  add_index :users, :some_column
411
412
  end
@@ -417,7 +418,7 @@ end
417
418
  Add indexes concurrently.
418
419
 
419
420
  ```ruby
420
- class AddSomeIndexToUsers < ActiveRecord::Migration[7.0]
421
+ class AddSomeIndexToUsers < ActiveRecord::Migration[7.1]
421
422
  disable_ddl_transaction!
422
423
 
423
424
  def change
@@ -443,7 +444,7 @@ rails g index table column
443
444
  Rails adds an index non-concurrently to references by default, which blocks writes in Postgres.
444
445
 
445
446
  ```ruby
446
- class AddReferenceToUsers < ActiveRecord::Migration[7.0]
447
+ class AddReferenceToUsers < ActiveRecord::Migration[7.1]
447
448
  def change
448
449
  add_reference :users, :city
449
450
  end
@@ -455,7 +456,7 @@ end
455
456
  Make sure the index is added concurrently.
456
457
 
457
458
  ```ruby
458
- class AddReferenceToUsers < ActiveRecord::Migration[7.0]
459
+ class AddReferenceToUsers < ActiveRecord::Migration[7.1]
459
460
  disable_ddl_transaction!
460
461
 
461
462
  def change
@@ -473,7 +474,7 @@ end
473
474
  In Postgres, adding a foreign key blocks writes on both tables.
474
475
 
475
476
  ```ruby
476
- class AddForeignKeyOnUsers < ActiveRecord::Migration[7.0]
477
+ class AddForeignKeyOnUsers < ActiveRecord::Migration[7.1]
477
478
  def change
478
479
  add_foreign_key :users, :orders
479
480
  end
@@ -483,7 +484,7 @@ end
483
484
  or
484
485
 
485
486
  ```ruby
486
- class AddReferenceToUsers < ActiveRecord::Migration[7.0]
487
+ class AddReferenceToUsers < ActiveRecord::Migration[7.1]
487
488
  def change
488
489
  add_reference :users, :order, foreign_key: true
489
490
  end
@@ -495,7 +496,7 @@ end
495
496
  Add the foreign key without validating existing rows:
496
497
 
497
498
  ```ruby
498
- class AddForeignKeyOnUsers < ActiveRecord::Migration[7.0]
499
+ class AddForeignKeyOnUsers < ActiveRecord::Migration[7.1]
499
500
  def change
500
501
  add_foreign_key :users, :orders, validate: false
501
502
  end
@@ -505,13 +506,46 @@ end
505
506
  Then validate them in a separate migration.
506
507
 
507
508
  ```ruby
508
- class ValidateForeignKeyOnUsers < ActiveRecord::Migration[7.0]
509
+ class ValidateForeignKeyOnUsers < ActiveRecord::Migration[7.1]
509
510
  def change
510
511
  validate_foreign_key :users, :orders
511
512
  end
512
513
  end
513
514
  ```
514
515
 
516
+ ### Adding a unique constraint
517
+
518
+ #### Bad
519
+
520
+ In Postgres, adding a unique constraint creates a unique index, which blocks reads and writes.
521
+
522
+ ```ruby
523
+ class AddUniqueContraint < ActiveRecord::Migration[7.1]
524
+ def change
525
+ add_unique_constraint :users, :some_column
526
+ end
527
+ end
528
+ ```
529
+
530
+ #### Good
531
+
532
+ Create a unique index concurrently, then use it for the constraint.
533
+
534
+ ```ruby
535
+ class AddUniqueContraint < ActiveRecord::Migration[7.1]
536
+ disable_ddl_transaction!
537
+
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
545
+ end
546
+ end
547
+ ```
548
+
515
549
  ### Adding an exclusion constraint
516
550
 
517
551
  #### Bad
@@ -537,7 +571,7 @@ end
537
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.
538
572
 
539
573
  ```ruby
540
- class AddPropertiesToUsers < ActiveRecord::Migration[7.0]
574
+ class AddPropertiesToUsers < ActiveRecord::Migration[7.1]
541
575
  def change
542
576
  add_column :users, :properties, :json
543
577
  end
@@ -549,7 +583,7 @@ end
549
583
  Use `jsonb` instead.
550
584
 
551
585
  ```ruby
552
- class AddPropertiesToUsers < ActiveRecord::Migration[7.0]
586
+ class AddPropertiesToUsers < ActiveRecord::Migration[7.1]
553
587
  def change
554
588
  add_column :users, :properties, :jsonb
555
589
  end
@@ -565,7 +599,7 @@ end
565
599
  In Postgres, setting `NOT NULL` on an existing column blocks reads and writes while every row is checked.
566
600
 
567
601
  ```ruby
568
- class SetSomeColumnNotNull < ActiveRecord::Migration[7.0]
602
+ class SetSomeColumnNotNull < ActiveRecord::Migration[7.1]
569
603
  def change
570
604
  change_column_null :users, :some_column, false
571
605
  end
@@ -576,10 +610,10 @@ end
576
610
 
577
611
  Instead, add a check constraint.
578
612
 
579
- For Rails 6.1, use:
613
+ For Rails 6.1+, use:
580
614
 
581
615
  ```ruby
582
- class SetSomeColumnNotNull < ActiveRecord::Migration[7.0]
616
+ class SetSomeColumnNotNull < ActiveRecord::Migration[7.1]
583
617
  def change
584
618
  add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
585
619
  end
@@ -600,10 +634,10 @@ end
600
634
 
601
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.
602
636
 
603
- For Rails 6.1, use:
637
+ For Rails 6.1+, use:
604
638
 
605
639
  ```ruby
606
- class ValidateSomeColumnNotNull < ActiveRecord::Migration[7.0]
640
+ class ValidateSomeColumnNotNull < ActiveRecord::Migration[7.1]
607
641
  def change
608
642
  validate_check_constraint :users, name: "users_some_column_null"
609
643
 
@@ -656,7 +690,7 @@ Disable partial writes in `config/application.rb`. For Rails < 7, use:
656
690
  config.active_record.partial_writes = false
657
691
  ```
658
692
 
659
- For Rails 7, use:
693
+ For Rails 7+, use:
660
694
 
661
695
  ```ruby
662
696
  config.active_record.partial_inserts = false
@@ -669,7 +703,7 @@ config.active_record.partial_inserts = false
669
703
  Adding a non-unique index with more than three columns rarely improves performance.
670
704
 
671
705
  ```ruby
672
- class AddSomeIndexToUsers < ActiveRecord::Migration[7.0]
706
+ class AddSomeIndexToUsers < ActiveRecord::Migration[7.1]
673
707
  def change
674
708
  add_index :users, [:a, :b, :c, :d]
675
709
  end
@@ -681,7 +715,7 @@ end
681
715
  Instead, start an index with columns that narrow down the results the most.
682
716
 
683
717
  ```ruby
684
- class AddSomeIndexToUsers < ActiveRecord::Migration[7.0]
718
+ class AddSomeIndexToUsers < ActiveRecord::Migration[7.1]
685
719
  def change
686
720
  add_index :users, [:b, :d]
687
721
  end
@@ -695,7 +729,7 @@ For Postgres, be sure to add them concurrently.
695
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.
696
730
 
697
731
  ```ruby
698
- class MySafeMigration < ActiveRecord::Migration[7.0]
732
+ class MySafeMigration < ActiveRecord::Migration[7.1]
699
733
  def change
700
734
  safety_assured { remove_column :users, :some_column }
701
735
  end
@@ -793,25 +827,6 @@ ALTER ROLE myuser SET statement_timeout = '1h';
793
827
 
794
828
  Note: If you use PgBouncer in transaction mode, you must set timeouts on the database user.
795
829
 
796
- ## Lock Timeout Retries [experimental]
797
-
798
- There’s the option to automatically retry statements when the lock timeout is reached. Here’s how it works:
799
-
800
- - If a lock timeout happens outside a transaction, the statement is retried
801
- - If it happens inside the DDL transaction, the entire migration is retried (only applicable to Postgres)
802
-
803
- Add to `config/initializers/strong_migrations.rb`:
804
-
805
- ```ruby
806
- StrongMigrations.lock_timeout_retries = 3
807
- ```
808
-
809
- Set the delay between retries with:
810
-
811
- ```ruby
812
- StrongMigrations.lock_timeout_retry_delay = 10.seconds
813
- ```
814
-
815
830
  ## App Timeouts
816
831
 
817
832
  We recommend adding timeouts to `config/database.yml` to prevent connections from hanging and individual queries from taking up too many resources in controllers, jobs, the Rails console, and other places.
@@ -855,12 +870,31 @@ production:
855
870
 
856
871
  For HTTP connections, Redis, and other services, check out [this guide](https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts).
857
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
+
858
892
  ## Existing Migrations
859
893
 
860
894
  To mark migrations as safe that were created before installing this gem, create an initializer with:
861
895
 
862
896
  ```ruby
863
- StrongMigrations.start_after = 20170101000000
897
+ StrongMigrations.start_after = 20230101000000
864
898
  ```
865
899
 
866
900
  Use the version from your latest migration.
@@ -48,6 +48,8 @@ module StrongMigrations
48
48
  check_add_index(*args)
49
49
  when :add_reference, :add_belongs_to
50
50
  check_add_reference(method, *args)
51
+ when :add_unique_constraint
52
+ check_add_unique_constraint(*args)
51
53
  when :change_column
52
54
  check_change_column(*args)
53
55
  when :change_column_default
@@ -123,6 +125,10 @@ module StrongMigrations
123
125
  end
124
126
  end
125
127
 
128
+ def version_safe?
129
+ version && version <= StrongMigrations.start_after
130
+ end
131
+
126
132
  private
127
133
 
128
134
  def check_version_supported
@@ -163,11 +169,7 @@ module StrongMigrations
163
169
  end
164
170
 
165
171
  def safe?
166
- self.class.safe || ENV["SAFETY_ASSURED"] || (direction == :down && !StrongMigrations.check_down) || version_safe?
167
- end
168
-
169
- def version_safe?
170
- version && version <= StrongMigrations.start_after
172
+ self.class.safe || ENV["SAFETY_ASSURED"] || (direction == :down && !StrongMigrations.check_down) || version_safe? || @migration.reverting?
171
173
  end
172
174
 
173
175
  def version
@@ -180,6 +180,21 @@ Then add the foreign key in separate migrations."
180
180
  end
181
181
  end
182
182
 
183
+ def check_add_unique_constraint(*args)
184
+ args.extract_options!
185
+ table, column = args
186
+
187
+ # column and using_index cannot be used together
188
+ # check for column to ensure error message can be generated
189
+ if column && !new_table?(table)
190
+ index_name = connection.index_name(table, {column: column})
191
+ raise_error :add_unique_constraint,
192
+ index_command: command_str(:add_index, [table, column, {unique: true, algorithm: :concurrently}]),
193
+ constraint_command: command_str(:add_unique_constraint, [table, {using_index: index_name}]),
194
+ remove_command: command_str(:remove_unique_constraint, [table, column])
195
+ end
196
+ end
197
+
183
198
  def check_change_column(*args)
184
199
  options = args.extract_options!
185
200
  table, column, type = args
@@ -88,7 +88,7 @@ in your application. A safer approach is to:
88
88
 
89
89
  1. Create a new column
90
90
  2. Write to both columns
91
- 3. Backfill data from the old column to new column
91
+ 3. Backfill data from the old column to the new column
92
92
  4. Move reads from the old column to the new column
93
93
  5. Stop writing to the old column
94
94
  6. Drop the old column",
@@ -99,7 +99,7 @@ in your application. A safer approach is to:
99
99
 
100
100
  1. Create a new table. Don't forget to recreate indexes from the old table
101
101
  2. Write to both tables
102
- 3. Backfill data from the old table to new table
102
+ 3. Backfill data from the old table to the new table
103
103
  4. Move reads from the old table to the new table
104
104
  5. Stop writing to the old table
105
105
  6. Drop the old table",
@@ -244,7 +244,24 @@ end",
244
244
  Use disable_ddl_transaction! or a separate migration.",
245
245
 
246
246
  add_exclusion_constraint:
247
- "Adding an exclusion constraint blocks reads and writes while every row is checked."
247
+ "Adding an exclusion constraint blocks reads and writes while every row is checked.",
248
+
249
+ add_unique_constraint:
250
+ "Adding a unique constraint creates a unique index, which blocks reads and writes.
251
+ Instead, create a unique index concurrently, then use it for the constraint.
252
+
253
+ class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
254
+ disable_ddl_transaction!
255
+
256
+ def up
257
+ %{index_command}
258
+ %{constraint_command}
259
+ end
260
+
261
+ def down
262
+ %{remove_command}
263
+ end
264
+ end"
248
265
  }
249
266
  self.enabled_checks = (error_messages.keys - [:remove_index]).map { |k| [k, {}] }.to_h
250
267
  end
@@ -20,6 +20,14 @@ module StrongMigrations
20
20
  end
21
21
  ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
22
22
 
23
+ def revert(*)
24
+ if strong_migrations_checker.version_safe?
25
+ safety_assured { super }
26
+ else
27
+ super
28
+ end
29
+ end
30
+
23
31
  def safety_assured
24
32
  strong_migrations_checker.class.safety_assured do
25
33
  yield
@@ -1,3 +1,3 @@
1
1
  module StrongMigrations
2
- VERSION = "1.6.3"
2
+ VERSION = "1.7.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strong_migrations
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.3
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-09-20 00:00:00.000000000 Z
13
+ date: 2024-01-05 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -75,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
75
75
  - !ruby/object:Gem::Version
76
76
  version: '0'
77
77
  requirements: []
78
- rubygems_version: 3.4.10
78
+ rubygems_version: 3.5.3
79
79
  signing_key:
80
80
  specification_version: 4
81
81
  summary: Catch unsafe migrations in development