strong_migrations 1.4.4 → 1.6.4

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: 7444023bd5f0829a289ebc9d222a28f041a4e3789224438f9fd6bffa01fa6668
4
- data.tar.gz: feb5010616cc8ef6cf36088be7a67bb0d74272cde79d0e9a0a442272bfa92d99
3
+ metadata.gz: 5399a374f21a5e61350adbe125184d43664bc654cd1583804e7b01c9d6240aab
4
+ data.tar.gz: 71e638db667d72b1fdf433eb27cc4f382a6472bd0f75885f5ae244990c1b768a
5
5
  SHA512:
6
- metadata.gz: '0599a3fef628ec724471ef53b7ad7358831e6b638bdd0ca3b7cb79c1b416da6c044793ecca5475f5e602e8a7d1235ced3ff3fda526f31b9c4a56990c8531f9b4'
7
- data.tar.gz: 6aac91ecb348c6a04c25511adf7df5d3e97823f6b9acdfbfd86bac50c4f5856f15fdcc271f00d0fc6f1e5d2cfdbaf3da5e056b1476aaf9b224e7df89b9737e01
6
+ metadata.gz: 4c8dbc405cb94e4b8eb92a08161cb8689996e7a6ba216b326db27a83151f5dd796d53c42e5b4dd2f8e0cb224f3f6c8f94ff81edd23063438a8143f0d36c507b9
7
+ data.tar.gz: 532f43609d8006f71e325a708a74c45df8161d5957a5173701c77cc22e7dd3111562cbc70b715c6f94c955d08cda0fd43cfc577b9d902cda8a9e1fedcd4693cc
data/CHANGELOG.md CHANGED
@@ -1,3 +1,29 @@
1
+ ## 1.6.4 (2023-10-17)
2
+
3
+ - Fixed false positives with `revert`
4
+
5
+ ## 1.6.3 (2023-09-20)
6
+
7
+ - Added support for Trilogy
8
+
9
+ ## 1.6.2 (2023-09-13)
10
+
11
+ - Fixed foreign key options with `add_reference` and `safe_by_default`
12
+ - Fixed `safety_assured` with `revert`
13
+
14
+ ## 1.6.1 (2023-08-09)
15
+
16
+ - Fixed `safety_assured` for custom checks with `safe_by_default`
17
+
18
+ ## 1.6.0 (2023-07-22)
19
+
20
+ - Added check for `change_column_default`
21
+
22
+ ## 1.5.0 (2023-07-02)
23
+
24
+ - Added check for `add_column` with stored generated columns
25
+ - Fixed `add_reference` with `foreign_key` and `index: false`
26
+
1
27
  ## 1.4.4 (2023-03-08)
2
28
 
3
29
  - Fixed `add_foreign_key` with `name` and `column` options with `safe_by_default`
@@ -219,7 +245,7 @@ Other
219
245
  ## 0.2.2 (2018-02-14)
220
246
 
221
247
  - Friendlier output
222
- - Better method of hooking into ActiveRecord
248
+ - Better method of hooking into Active Record
223
249
 
224
250
  ## 0.2.1 (2018-02-07)
225
251
 
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[7.0]
46
+ class RemoveColumn < ActiveRecord::Migration[7.1]
47
47
  def change
48
48
  safety_assured { remove_column :users, :name }
49
49
  end
@@ -62,6 +62,7 @@ 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)
@@ -78,6 +79,10 @@ Postgres-specific checks:
78
79
  - [adding a json column](#adding-a-json-column)
79
80
  - [setting NOT NULL on an existing column](#setting-not-null-on-an-existing-column)
80
81
 
82
+ Config-specific checks:
83
+
84
+ - [changing the default value of a column](#changing-the-default-value-of-a-column)
85
+
81
86
  Best practices:
82
87
 
83
88
  - [keeping non-unique indexes to three columns or less](#keeping-non-unique-indexes-to-three-columns-or-less)
@@ -91,7 +96,7 @@ You can also add [custom checks](#custom-checks) or [disable specific checks](#d
91
96
  Active Record caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
92
97
 
93
98
  ```ruby
94
- class RemoveSomeColumnFromUsers < ActiveRecord::Migration[7.0]
99
+ class RemoveSomeColumnFromUsers < ActiveRecord::Migration[7.1]
95
100
  def change
96
101
  remove_column :users, :some_column
97
102
  end
@@ -112,7 +117,7 @@ end
112
117
  3. Write a migration to remove the column (wrap in `safety_assured` block)
113
118
 
114
119
  ```ruby
115
- class RemoveSomeColumnFromUsers < ActiveRecord::Migration[7.0]
120
+ class RemoveSomeColumnFromUsers < ActiveRecord::Migration[7.1]
116
121
  def change
117
122
  safety_assured { remove_column :users, :some_column }
118
123
  end
@@ -129,7 +134,7 @@ end
129
134
  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.
130
135
 
131
136
  ```ruby
132
- class AddSomeColumnToUsers < ActiveRecord::Migration[7.0]
137
+ class AddSomeColumnToUsers < ActiveRecord::Migration[7.1]
133
138
  def change
134
139
  add_column :users, :some_column, :text, default: "default_value"
135
140
  end
@@ -143,7 +148,7 @@ In Postgres 11+, MySQL 8.0.12+, and MariaDB 10.3.2+, this no longer requires a t
143
148
  Instead, add the column without a default value, then change the default.
144
149
 
145
150
  ```ruby
146
- class AddSomeColumnToUsers < ActiveRecord::Migration[7.0]
151
+ class AddSomeColumnToUsers < ActiveRecord::Migration[7.1]
147
152
  def up
148
153
  add_column :users, :some_column, :text
149
154
  change_column_default :users, :some_column, "default_value"
@@ -164,7 +169,7 @@ See the next section for how to backfill.
164
169
  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/).
165
170
 
166
171
  ```ruby
167
- class AddSomeColumnToUsers < ActiveRecord::Migration[7.0]
172
+ class AddSomeColumnToUsers < ActiveRecord::Migration[7.1]
168
173
  def change
169
174
  add_column :users, :some_column, :text
170
175
  User.update_all some_column: "default_value"
@@ -179,7 +184,7 @@ Also, running a single query to update data can cause issues for large tables.
179
184
  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!`.
180
185
 
181
186
  ```ruby
182
- class BackfillSomeColumn < ActiveRecord::Migration[7.0]
187
+ class BackfillSomeColumn < ActiveRecord::Migration[7.1]
183
188
  disable_ddl_transaction!
184
189
 
185
190
  def up
@@ -191,6 +196,24 @@ class BackfillSomeColumn < ActiveRecord::Migration[7.0]
191
196
  end
192
197
  ```
193
198
 
199
+ ### Adding a stored generated column
200
+
201
+ #### Bad
202
+
203
+ 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
+ ```ruby
206
+ class AddSomeColumnToUsers < ActiveRecord::Migration[7.1]
207
+ def change
208
+ add_column :users, :some_column, :virtual, type: :string, as: "...", stored: true
209
+ end
210
+ end
211
+ ```
212
+
213
+ #### Good
214
+
215
+ Add a non-generated column and use callbacks or triggers instead (or a virtual generated column with MySQL and MariaDB).
216
+
194
217
  ### Changing the type of a column
195
218
 
196
219
  #### Bad
@@ -198,7 +221,7 @@ end
198
221
  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.
199
222
 
200
223
  ```ruby
201
- class ChangeSomeColumnType < ActiveRecord::Migration[7.0]
224
+ class ChangeSomeColumnType < ActiveRecord::Migration[7.1]
202
225
  def change
203
226
  change_column :users, :some_column, :new_type
204
227
  end
@@ -244,7 +267,7 @@ A safer approach is to:
244
267
  Renaming a column that’s in use will cause errors in your application.
245
268
 
246
269
  ```ruby
247
- class RenameSomeColumn < ActiveRecord::Migration[7.0]
270
+ class RenameSomeColumn < ActiveRecord::Migration[7.1]
248
271
  def change
249
272
  rename_column :users, :some_column, :new_name
250
273
  end
@@ -269,7 +292,7 @@ A safer approach is to:
269
292
  Renaming a table that’s in use will cause errors in your application.
270
293
 
271
294
  ```ruby
272
- class RenameUsersToCustomers < ActiveRecord::Migration[7.0]
295
+ class RenameUsersToCustomers < ActiveRecord::Migration[7.1]
273
296
  def change
274
297
  rename_table :users, :customers
275
298
  end
@@ -294,7 +317,7 @@ A safer approach is to:
294
317
  The `force` option can drop an existing table.
295
318
 
296
319
  ```ruby
297
- class CreateUsers < ActiveRecord::Migration[7.0]
320
+ class CreateUsers < ActiveRecord::Migration[7.1]
298
321
  def change
299
322
  create_table :users, force: true do |t|
300
323
  # ...
@@ -308,7 +331,7 @@ end
308
331
  Create tables without the `force` option.
309
332
 
310
333
  ```ruby
311
- class CreateUsers < ActiveRecord::Migration[7.0]
334
+ class CreateUsers < ActiveRecord::Migration[7.1]
312
335
  def change
313
336
  create_table :users do |t|
314
337
  # ...
@@ -328,7 +351,7 @@ If you intend to drop an existing table, run `drop_table` first.
328
351
  Adding a check constraint blocks reads and writes in Postgres and blocks writes in MySQL and MariaDB while every row is checked.
329
352
 
330
353
  ```ruby
331
- class AddCheckConstraint < ActiveRecord::Migration[7.0]
354
+ class AddCheckConstraint < ActiveRecord::Migration[7.1]
332
355
  def change
333
356
  add_check_constraint :users, "price > 0", name: "price_check"
334
357
  end
@@ -340,7 +363,7 @@ end
340
363
  Add the check constraint without validating existing rows:
341
364
 
342
365
  ```ruby
343
- class AddCheckConstraint < ActiveRecord::Migration[7.0]
366
+ class AddCheckConstraint < ActiveRecord::Migration[7.1]
344
367
  def change
345
368
  add_check_constraint :users, "price > 0", name: "price_check", validate: false
346
369
  end
@@ -350,7 +373,7 @@ end
350
373
  Then validate them in a separate migration.
351
374
 
352
375
  ```ruby
353
- class ValidateCheckConstraint < ActiveRecord::Migration[7.0]
376
+ class ValidateCheckConstraint < ActiveRecord::Migration[7.1]
354
377
  def change
355
378
  validate_check_constraint :users, name: "price_check"
356
379
  end
@@ -366,7 +389,7 @@ end
366
389
  Strong Migrations can’t ensure safety for raw SQL statements. Make really sure that what you’re doing is safe, then use:
367
390
 
368
391
  ```ruby
369
- class ExecuteSQL < ActiveRecord::Migration[7.0]
392
+ class ExecuteSQL < ActiveRecord::Migration[7.1]
370
393
  def change
371
394
  safety_assured { execute "..." }
372
395
  end
@@ -382,7 +405,7 @@ end
382
405
  In Postgres, adding an index non-concurrently blocks writes.
383
406
 
384
407
  ```ruby
385
- class AddSomeIndexToUsers < ActiveRecord::Migration[7.0]
408
+ class AddSomeIndexToUsers < ActiveRecord::Migration[7.1]
386
409
  def change
387
410
  add_index :users, :some_column
388
411
  end
@@ -394,7 +417,7 @@ end
394
417
  Add indexes concurrently.
395
418
 
396
419
  ```ruby
397
- class AddSomeIndexToUsers < ActiveRecord::Migration[7.0]
420
+ class AddSomeIndexToUsers < ActiveRecord::Migration[7.1]
398
421
  disable_ddl_transaction!
399
422
 
400
423
  def change
@@ -420,7 +443,7 @@ rails g index table column
420
443
  Rails adds an index non-concurrently to references by default, which blocks writes in Postgres.
421
444
 
422
445
  ```ruby
423
- class AddReferenceToUsers < ActiveRecord::Migration[7.0]
446
+ class AddReferenceToUsers < ActiveRecord::Migration[7.1]
424
447
  def change
425
448
  add_reference :users, :city
426
449
  end
@@ -432,7 +455,7 @@ end
432
455
  Make sure the index is added concurrently.
433
456
 
434
457
  ```ruby
435
- class AddReferenceToUsers < ActiveRecord::Migration[7.0]
458
+ class AddReferenceToUsers < ActiveRecord::Migration[7.1]
436
459
  disable_ddl_transaction!
437
460
 
438
461
  def change
@@ -450,7 +473,7 @@ end
450
473
  In Postgres, adding a foreign key blocks writes on both tables.
451
474
 
452
475
  ```ruby
453
- class AddForeignKeyOnUsers < ActiveRecord::Migration[7.0]
476
+ class AddForeignKeyOnUsers < ActiveRecord::Migration[7.1]
454
477
  def change
455
478
  add_foreign_key :users, :orders
456
479
  end
@@ -460,7 +483,7 @@ end
460
483
  or
461
484
 
462
485
  ```ruby
463
- class AddReferenceToUsers < ActiveRecord::Migration[7.0]
486
+ class AddReferenceToUsers < ActiveRecord::Migration[7.1]
464
487
  def change
465
488
  add_reference :users, :order, foreign_key: true
466
489
  end
@@ -472,7 +495,7 @@ end
472
495
  Add the foreign key without validating existing rows:
473
496
 
474
497
  ```ruby
475
- class AddForeignKeyOnUsers < ActiveRecord::Migration[7.0]
498
+ class AddForeignKeyOnUsers < ActiveRecord::Migration[7.1]
476
499
  def change
477
500
  add_foreign_key :users, :orders, validate: false
478
501
  end
@@ -482,7 +505,7 @@ end
482
505
  Then validate them in a separate migration.
483
506
 
484
507
  ```ruby
485
- class ValidateForeignKeyOnUsers < ActiveRecord::Migration[7.0]
508
+ class ValidateForeignKeyOnUsers < ActiveRecord::Migration[7.1]
486
509
  def change
487
510
  validate_foreign_key :users, :orders
488
511
  end
@@ -514,7 +537,7 @@ end
514
537
  In Postgres, there’s no equality operator for the `json` column type, which can cause errors for existing `SELECT DISTINCT` queries in your application.
515
538
 
516
539
  ```ruby
517
- class AddPropertiesToUsers < ActiveRecord::Migration[7.0]
540
+ class AddPropertiesToUsers < ActiveRecord::Migration[7.1]
518
541
  def change
519
542
  add_column :users, :properties, :json
520
543
  end
@@ -526,7 +549,7 @@ end
526
549
  Use `jsonb` instead.
527
550
 
528
551
  ```ruby
529
- class AddPropertiesToUsers < ActiveRecord::Migration[7.0]
552
+ class AddPropertiesToUsers < ActiveRecord::Migration[7.1]
530
553
  def change
531
554
  add_column :users, :properties, :jsonb
532
555
  end
@@ -542,7 +565,7 @@ end
542
565
  In Postgres, setting `NOT NULL` on an existing column blocks reads and writes while every row is checked.
543
566
 
544
567
  ```ruby
545
- class SetSomeColumnNotNull < ActiveRecord::Migration[7.0]
568
+ class SetSomeColumnNotNull < ActiveRecord::Migration[7.1]
546
569
  def change
547
570
  change_column_null :users, :some_column, false
548
571
  end
@@ -553,10 +576,10 @@ end
553
576
 
554
577
  Instead, add a check constraint.
555
578
 
556
- For Rails 6.1, use:
579
+ For Rails 6.1+, use:
557
580
 
558
581
  ```ruby
559
- class SetSomeColumnNotNull < ActiveRecord::Migration[7.0]
582
+ class SetSomeColumnNotNull < ActiveRecord::Migration[7.1]
560
583
  def change
561
584
  add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
562
585
  end
@@ -577,10 +600,10 @@ end
577
600
 
578
601
  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.
579
602
 
580
- For Rails 6.1, use:
603
+ For Rails 6.1+, use:
581
604
 
582
605
  ```ruby
583
- class ValidateSomeColumnNotNull < ActiveRecord::Migration[7.0]
606
+ class ValidateSomeColumnNotNull < ActiveRecord::Migration[7.1]
584
607
  def change
585
608
  validate_check_constraint :users, name: "users_some_column_null"
586
609
 
@@ -609,6 +632,36 @@ class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
609
632
  end
610
633
  ```
611
634
 
635
+ ### Changing the default value of a column
636
+
637
+ #### Bad
638
+
639
+ Rails < 7 enables partial writes by default, which can cause incorrect values to be inserted when changing the default value of a column.
640
+
641
+ ```ruby
642
+ class ChangeSomeColumnDefault < ActiveRecord::Migration[6.1]
643
+ def change
644
+ change_column_default :users, :some_column, from: "old", to: "new"
645
+ end
646
+ end
647
+
648
+ User.create!(some_column: "old") # can insert "new"
649
+ ```
650
+
651
+ #### Good
652
+
653
+ Disable partial writes in `config/application.rb`. For Rails < 7, use:
654
+
655
+ ```ruby
656
+ config.active_record.partial_writes = false
657
+ ```
658
+
659
+ For Rails 7+, use:
660
+
661
+ ```ruby
662
+ config.active_record.partial_inserts = false
663
+ ```
664
+
612
665
  ### Keeping non-unique indexes to three columns or less
613
666
 
614
667
  #### Bad
@@ -616,7 +669,7 @@ end
616
669
  Adding a non-unique index with more than three columns rarely improves performance.
617
670
 
618
671
  ```ruby
619
- class AddSomeIndexToUsers < ActiveRecord::Migration[7.0]
672
+ class AddSomeIndexToUsers < ActiveRecord::Migration[7.1]
620
673
  def change
621
674
  add_index :users, [:a, :b, :c, :d]
622
675
  end
@@ -628,7 +681,7 @@ end
628
681
  Instead, start an index with columns that narrow down the results the most.
629
682
 
630
683
  ```ruby
631
- class AddSomeIndexToUsers < ActiveRecord::Migration[7.0]
684
+ class AddSomeIndexToUsers < ActiveRecord::Migration[7.1]
632
685
  def change
633
686
  add_index :users, [:b, :d]
634
687
  end
@@ -642,7 +695,7 @@ For Postgres, be sure to add them concurrently.
642
695
  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.
643
696
 
644
697
  ```ruby
645
- class MySafeMigration < ActiveRecord::Migration[7.0]
698
+ class MySafeMigration < ActiveRecord::Migration[7.1]
646
699
  def change
647
700
  safety_assured { remove_column :users, :some_column }
648
701
  end
@@ -740,25 +793,6 @@ ALTER ROLE myuser SET statement_timeout = '1h';
740
793
 
741
794
  Note: If you use PgBouncer in transaction mode, you must set timeouts on the database user.
742
795
 
743
- ## Lock Timeout Retries [experimental]
744
-
745
- There’s the option to automatically retry statements when the lock timeout is reached. Here’s how it works:
746
-
747
- - If a lock timeout happens outside a transaction, the statement is retried
748
- - If it happens inside the DDL transaction, the entire migration is retried (only applicable to Postgres)
749
-
750
- Add to `config/initializers/strong_migrations.rb`:
751
-
752
- ```ruby
753
- StrongMigrations.lock_timeout_retries = 3
754
- ```
755
-
756
- Set the delay between retries with:
757
-
758
- ```ruby
759
- StrongMigrations.lock_timeout_retry_delay = 10.seconds
760
- ```
761
-
762
796
  ## App Timeouts
763
797
 
764
798
  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.
@@ -802,12 +836,31 @@ production:
802
836
 
803
837
  For HTTP connections, Redis, and other services, check out [this guide](https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts).
804
838
 
839
+ ## Lock Timeout Retries [experimental]
840
+
841
+ There’s the option to automatically retry statements for migrations when the lock timeout is reached. Here’s how it works:
842
+
843
+ - If a lock timeout happens outside a transaction, the statement is retried
844
+ - If it happens inside the DDL transaction, the entire migration is retried (only applicable to Postgres)
845
+
846
+ Add to `config/initializers/strong_migrations.rb`:
847
+
848
+ ```ruby
849
+ StrongMigrations.lock_timeout_retries = 3
850
+ ```
851
+
852
+ Set the delay between retries with:
853
+
854
+ ```ruby
855
+ StrongMigrations.lock_timeout_retry_delay = 10.seconds
856
+ ```
857
+
805
858
  ## Existing Migrations
806
859
 
807
860
  To mark migrations as safe that were created before installing this gem, create an initializer with:
808
861
 
809
862
  ```ruby
810
- StrongMigrations.start_after = 20170101000000
863
+ StrongMigrations.start_after = 20230101000000
811
864
  ```
812
865
 
813
866
  Use the version from your latest migration.
@@ -5,21 +5,25 @@ module StrongMigrations
5
5
 
6
6
  attr_accessor :direction, :transaction_disabled, :timeouts_set
7
7
 
8
+ class << self
9
+ attr_accessor :safe
10
+ end
11
+
8
12
  def initialize(migration)
9
13
  @migration = migration
10
14
  @new_tables = []
11
- @safe = false
15
+ @new_columns = []
12
16
  @timeouts_set = false
13
17
  @committed = false
14
18
  end
15
19
 
16
- def safety_assured
17
- previous_value = @safe
20
+ def self.safety_assured
21
+ previous_value = safe
18
22
  begin
19
- @safe = true
23
+ self.safe = true
20
24
  yield
21
25
  ensure
22
- @safe = previous_value
26
+ self.safe = previous_value
23
27
  end
24
28
  end
25
29
 
@@ -46,6 +50,8 @@ module StrongMigrations
46
50
  check_add_reference(method, *args)
47
51
  when :change_column
48
52
  check_change_column(*args)
53
+ when :change_column_default
54
+ check_change_column_default(*args)
49
55
  when :change_column_null
50
56
  check_change_column_null(*args)
51
57
  when :change_table
@@ -75,9 +81,11 @@ module StrongMigrations
75
81
  @committed = true
76
82
  end
77
83
 
78
- # custom checks
79
- StrongMigrations.checks.each do |check|
80
- @migration.instance_exec(method, args, &check)
84
+ if !safe?
85
+ # custom checks
86
+ StrongMigrations.checks.each do |check|
87
+ @migration.instance_exec(method, args, &check)
88
+ end
81
89
  end
82
90
  end
83
91
 
@@ -115,6 +123,10 @@ module StrongMigrations
115
123
  end
116
124
  end
117
125
 
126
+ def version_safe?
127
+ version && version <= StrongMigrations.start_after
128
+ end
129
+
118
130
  private
119
131
 
120
132
  def check_version_supported
@@ -155,11 +167,7 @@ module StrongMigrations
155
167
  end
156
168
 
157
169
  def safe?
158
- @safe || ENV["SAFETY_ASSURED"] || (direction == :down && !StrongMigrations.check_down) || version_safe?
159
- end
160
-
161
- def version_safe?
162
- version && version <= StrongMigrations.start_after
170
+ self.class.safe || ENV["SAFETY_ASSURED"] || (direction == :down && !StrongMigrations.check_down) || version_safe? || @migration.reverting?
163
171
  end
164
172
 
165
173
  def version
@@ -172,7 +180,7 @@ module StrongMigrations
172
180
  case connection.adapter_name
173
181
  when /postg/i # PostgreSQL, PostGIS
174
182
  Adapters::PostgreSQLAdapter
175
- when /mysql/i
183
+ when /mysql|trilogy/i
176
184
  if connection.try(:mariadb?)
177
185
  Adapters::MariaDBAdapter
178
186
  else
@@ -32,6 +32,9 @@ module StrongMigrations
32
32
  table, column, type = args
33
33
  default = options[:default]
34
34
 
35
+ # keep track of new columns of change_column_default check
36
+ @new_columns << [table.to_s, column.to_s]
37
+
35
38
  # Check key since DEFAULT NULL behaves differently from no default
36
39
  #
37
40
  # Also, Active Record has special case for uuid columns that allows function default values
@@ -71,6 +74,10 @@ Then add the NOT NULL constraint in separate migrations."
71
74
  raise_error :add_column_json,
72
75
  command: command_str("add_column", [table, column, :jsonb, options])
73
76
  end
77
+
78
+ if type.to_s == "virtual" && options[:stored]
79
+ raise_error :add_column_generated_stored, rewrite_blocks: adapter.rewrite_blocks
80
+ end
74
81
  end
75
82
 
76
83
  def check_add_exclusion_constraint(*args)
@@ -147,7 +154,7 @@ Then add the NOT NULL constraint in separate migrations."
147
154
  if bad_index || options[:foreign_key]
148
155
  if index_value.is_a?(Hash)
149
156
  options[:index] = options[:index].merge(algorithm: :concurrently)
150
- else
157
+ elsif index_value
151
158
  options = options.merge(index: {algorithm: :concurrently})
152
159
  end
153
160
 
@@ -194,6 +201,18 @@ Then add the foreign key in separate migrations."
194
201
  raise_error :change_column, rewrite_blocks: adapter.rewrite_blocks unless safe
195
202
  end
196
203
 
204
+ def check_change_column_default(*args)
205
+ table, column, _default_or_changes = args
206
+
207
+ # just check ActiveRecord::Base, even though can override on model
208
+ partial_inserts = ar_version >= 7 ? ActiveRecord::Base.partial_inserts : ActiveRecord::Base.partial_writes
209
+
210
+ if partial_inserts && !new_column?(table, column)
211
+ raise_error :change_column_default,
212
+ config: ar_version >= 7 ? "partial_inserts" : "partial_writes"
213
+ end
214
+ end
215
+
197
216
  def check_change_column_null(*args)
198
217
  table, column, null, default = args
199
218
  if !null
@@ -456,5 +475,9 @@ Then add the foreign key in separate migrations."
456
475
  def new_table?(table)
457
476
  @new_tables.include?(table.to_s)
458
477
  end
478
+
479
+ def new_column?(table, column)
480
+ new_table?(table) || @new_columns.include?([table.to_s, column.to_s])
481
+ end
459
482
  end
460
483
  end
@@ -50,6 +50,9 @@ class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
50
50
  end
51
51
  end",
52
52
 
53
+ add_column_generated_stored:
54
+ "Adding a stored generated column blocks %{rewrite_blocks} while the entire table is rewritten.",
55
+
53
56
  change_column:
54
57
  "Changing the type of an existing column blocks %{rewrite_blocks}
55
58
  while the entire table is rewritten. A safer approach is to:
@@ -158,6 +161,13 @@ Otherwise, remove the force option.",
158
161
  execute call, so cannot help you here. Please make really sure that what
159
162
  you're doing is safe before proceeding, then wrap it in a safety_assured { ... } block.",
160
163
 
164
+ change_column_default:
165
+ "Partial writes are enabled, which can cause incorrect values
166
+ to be inserted when changing the default value of a column.
167
+ Disable partial writes in config/application.rb:
168
+
169
+ config.active_record.%{config} = false",
170
+
161
171
  change_column_null:
162
172
  "Passing a default value to change_column_null runs a single UPDATE query,
163
173
  which can cause downtime. Instead, backfill the existing rows in the
@@ -20,8 +20,16 @@ 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
- strong_migrations_checker.safety_assured do
32
+ strong_migrations_checker.class.safety_assured do
25
33
  yield
26
34
  end
27
35
  end
@@ -30,10 +30,11 @@ module StrongMigrations
30
30
  (ActiveRecord::Base.pluralize_table_names ? reference.to_s.pluralize : reference).to_sym
31
31
  end
32
32
 
33
+ foreign_key_opts = foreign_key.is_a?(Hash) ? foreign_key.except(:to_table) : {}
33
34
  if reference
34
- @migration.add_foreign_key(table, name, column: "#{reference}_id")
35
+ @migration.add_foreign_key(table, name, column: "#{reference}_id", **foreign_key_opts)
35
36
  else
36
- @migration.add_foreign_key(table, name)
37
+ @migration.add_foreign_key(table, name, **foreign_key_opts)
37
38
  end
38
39
  end
39
40
  end
@@ -1,3 +1,3 @@
1
1
  module StrongMigrations
2
- VERSION = "1.4.4"
2
+ VERSION = "1.6.4"
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.4.4
4
+ version: 1.6.4
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-03-08 00:00:00.000000000 Z
13
+ date: 2023-10-17 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.6
78
+ rubygems_version: 3.4.10
79
79
  signing_key:
80
80
  specification_version: 4
81
81
  summary: Catch unsafe migrations in development