strong_migrations 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +42 -39
- data/lib/strong_migrations/schema_dumper.rb +11 -0
- data/lib/strong_migrations/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f129ef6d517a5adcc4091152f856c32b06cc0b5e4f8f2f3c0ff9ca854ba966c9
|
4
|
+
data.tar.gz: c649028a58aa6f11c598fd5b23e5a2ccec85ffc5b9de596c62ee7a6385f26b1e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba0ad1239d5dae5f01b2f92e00f3f5296241d5f9b908ab7d7f342ce1c7136b9c681973e8dc2752e3a25da333eaa880091cb352170f29a645a9209e45babaa965
|
7
|
+
data.tar.gz: 4764de588facf1cafa101fd9fdffc9bc27c821eff7613c9ca8c5e6e877674495d53c3bd8f5a45bcc8abfe2ca0105f53a8ad950c62c713eed01e3891619790553
|
data/CHANGELOG.md
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[7.
|
46
|
+
class RemoveColumn < ActiveRecord::Migration[7.2]
|
47
47
|
def change
|
48
48
|
safety_assured { remove_column :users, :name }
|
49
49
|
end
|
@@ -98,7 +98,7 @@ You can also add [custom checks](#custom-checks) or [disable specific checks](#d
|
|
98
98
|
Active Record caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
|
99
99
|
|
100
100
|
```ruby
|
101
|
-
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[7.
|
101
|
+
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[7.2]
|
102
102
|
def change
|
103
103
|
remove_column :users, :some_column
|
104
104
|
end
|
@@ -119,7 +119,7 @@ end
|
|
119
119
|
3. Write a migration to remove the column (wrap in `safety_assured` block)
|
120
120
|
|
121
121
|
```ruby
|
122
|
-
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[7.
|
122
|
+
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[7.2]
|
123
123
|
def change
|
124
124
|
safety_assured { remove_column :users, :some_column }
|
125
125
|
end
|
@@ -136,7 +136,7 @@ end
|
|
136
136
|
Changing the type of a column causes the entire table to be rewritten. During this time, reads and writes are blocked in Postgres, and writes are blocked in MySQL and MariaDB.
|
137
137
|
|
138
138
|
```ruby
|
139
|
-
class ChangeSomeColumnType < ActiveRecord::Migration[7.
|
139
|
+
class ChangeSomeColumnType < ActiveRecord::Migration[7.2]
|
140
140
|
def change
|
141
141
|
change_column :users, :some_column, :new_type
|
142
142
|
end
|
@@ -182,7 +182,7 @@ A safer approach is to:
|
|
182
182
|
Renaming a column that’s in use will cause errors in your application.
|
183
183
|
|
184
184
|
```ruby
|
185
|
-
class RenameSomeColumn < ActiveRecord::Migration[7.
|
185
|
+
class RenameSomeColumn < ActiveRecord::Migration[7.2]
|
186
186
|
def change
|
187
187
|
rename_column :users, :some_column, :new_name
|
188
188
|
end
|
@@ -207,7 +207,7 @@ A safer approach is to:
|
|
207
207
|
Renaming a table that’s in use will cause errors in your application.
|
208
208
|
|
209
209
|
```ruby
|
210
|
-
class RenameUsersToCustomers < ActiveRecord::Migration[7.
|
210
|
+
class RenameUsersToCustomers < ActiveRecord::Migration[7.2]
|
211
211
|
def change
|
212
212
|
rename_table :users, :customers
|
213
213
|
end
|
@@ -232,7 +232,7 @@ A safer approach is to:
|
|
232
232
|
The `force` option can drop an existing table.
|
233
233
|
|
234
234
|
```ruby
|
235
|
-
class CreateUsers < ActiveRecord::Migration[7.
|
235
|
+
class CreateUsers < ActiveRecord::Migration[7.2]
|
236
236
|
def change
|
237
237
|
create_table :users, force: true do |t|
|
238
238
|
# ...
|
@@ -246,7 +246,7 @@ end
|
|
246
246
|
Create tables without the `force` option.
|
247
247
|
|
248
248
|
```ruby
|
249
|
-
class CreateUsers < ActiveRecord::Migration[7.
|
249
|
+
class CreateUsers < ActiveRecord::Migration[7.2]
|
250
250
|
def change
|
251
251
|
create_table :users do |t|
|
252
252
|
# ...
|
@@ -264,7 +264,7 @@ If you intend to drop an existing table, run `drop_table` first.
|
|
264
264
|
Adding an auto-incrementing column (`serial`/`bigserial` in Postgres and `AUTO_INCREMENT` in MySQL and MariaDB) causes the entire table to be rewritten. During this time, reads and writes are blocked in Postgres, and writes are blocked in MySQL and MariaDB.
|
265
265
|
|
266
266
|
```ruby
|
267
|
-
class AddIdToCitiesUsers < ActiveRecord::Migration[7.
|
267
|
+
class AddIdToCitiesUsers < ActiveRecord::Migration[7.2]
|
268
268
|
def change
|
269
269
|
add_column :cities_users, :id, :primary_key
|
270
270
|
end
|
@@ -284,7 +284,7 @@ Create a new table and migrate the data with the same steps as [renaming a table
|
|
284
284
|
Adding a stored generated column causes the entire table to be rewritten. During this time, reads and writes are blocked in Postgres, and writes are blocked in MySQL and MariaDB.
|
285
285
|
|
286
286
|
```ruby
|
287
|
-
class AddSomeColumnToUsers < ActiveRecord::Migration[7.
|
287
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[7.2]
|
288
288
|
def change
|
289
289
|
add_column :users, :some_column, :virtual, type: :string, as: "...", stored: true
|
290
290
|
end
|
@@ -304,7 +304,7 @@ Add a non-generated column and use callbacks or triggers instead (or a virtual g
|
|
304
304
|
Adding a check constraint blocks reads and writes in Postgres and blocks writes in MySQL and MariaDB while every row is checked.
|
305
305
|
|
306
306
|
```ruby
|
307
|
-
class AddCheckConstraint < ActiveRecord::Migration[7.
|
307
|
+
class AddCheckConstraint < ActiveRecord::Migration[7.2]
|
308
308
|
def change
|
309
309
|
add_check_constraint :users, "price > 0", name: "price_check"
|
310
310
|
end
|
@@ -316,7 +316,7 @@ end
|
|
316
316
|
Add the check constraint without validating existing rows:
|
317
317
|
|
318
318
|
```ruby
|
319
|
-
class AddCheckConstraint < ActiveRecord::Migration[7.
|
319
|
+
class AddCheckConstraint < ActiveRecord::Migration[7.2]
|
320
320
|
def change
|
321
321
|
add_check_constraint :users, "price > 0", name: "price_check", validate: false
|
322
322
|
end
|
@@ -326,7 +326,7 @@ end
|
|
326
326
|
Then validate them in a separate migration.
|
327
327
|
|
328
328
|
```ruby
|
329
|
-
class ValidateCheckConstraint < ActiveRecord::Migration[7.
|
329
|
+
class ValidateCheckConstraint < ActiveRecord::Migration[7.2]
|
330
330
|
def change
|
331
331
|
validate_check_constraint :users, name: "price_check"
|
332
332
|
end
|
@@ -342,7 +342,7 @@ end
|
|
342
342
|
Strong Migrations can’t ensure safety for raw SQL statements. Make really sure that what you’re doing is safe, then use:
|
343
343
|
|
344
344
|
```ruby
|
345
|
-
class ExecuteSQL < ActiveRecord::Migration[7.
|
345
|
+
class ExecuteSQL < ActiveRecord::Migration[7.2]
|
346
346
|
def change
|
347
347
|
safety_assured { execute "..." }
|
348
348
|
end
|
@@ -356,7 +356,7 @@ end
|
|
356
356
|
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
357
|
|
358
358
|
```ruby
|
359
|
-
class AddSomeColumnToUsers < ActiveRecord::Migration[7.
|
359
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[7.2]
|
360
360
|
def change
|
361
361
|
add_column :users, :some_column, :text
|
362
362
|
User.update_all some_column: "default_value"
|
@@ -371,7 +371,7 @@ Also, running a single query to update data can cause issues for large tables.
|
|
371
371
|
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
372
|
|
373
373
|
```ruby
|
374
|
-
class BackfillSomeColumn < ActiveRecord::Migration[7.
|
374
|
+
class BackfillSomeColumn < ActiveRecord::Migration[7.2]
|
375
375
|
disable_ddl_transaction!
|
376
376
|
|
377
377
|
def up
|
@@ -392,7 +392,7 @@ end
|
|
392
392
|
In Postgres, adding an index non-concurrently blocks writes.
|
393
393
|
|
394
394
|
```ruby
|
395
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[7.
|
395
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[7.2]
|
396
396
|
def change
|
397
397
|
add_index :users, :some_column
|
398
398
|
end
|
@@ -404,7 +404,7 @@ end
|
|
404
404
|
Add indexes concurrently.
|
405
405
|
|
406
406
|
```ruby
|
407
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[7.
|
407
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[7.2]
|
408
408
|
disable_ddl_transaction!
|
409
409
|
|
410
410
|
def change
|
@@ -430,7 +430,7 @@ rails g index table column
|
|
430
430
|
Rails adds an index non-concurrently to references by default, which blocks writes in Postgres.
|
431
431
|
|
432
432
|
```ruby
|
433
|
-
class AddReferenceToUsers < ActiveRecord::Migration[7.
|
433
|
+
class AddReferenceToUsers < ActiveRecord::Migration[7.2]
|
434
434
|
def change
|
435
435
|
add_reference :users, :city
|
436
436
|
end
|
@@ -442,7 +442,7 @@ end
|
|
442
442
|
Make sure the index is added concurrently.
|
443
443
|
|
444
444
|
```ruby
|
445
|
-
class AddReferenceToUsers < ActiveRecord::Migration[7.
|
445
|
+
class AddReferenceToUsers < ActiveRecord::Migration[7.2]
|
446
446
|
disable_ddl_transaction!
|
447
447
|
|
448
448
|
def change
|
@@ -460,7 +460,7 @@ end
|
|
460
460
|
In Postgres, adding a foreign key blocks writes on both tables.
|
461
461
|
|
462
462
|
```ruby
|
463
|
-
class AddForeignKeyOnUsers < ActiveRecord::Migration[7.
|
463
|
+
class AddForeignKeyOnUsers < ActiveRecord::Migration[7.2]
|
464
464
|
def change
|
465
465
|
add_foreign_key :users, :orders
|
466
466
|
end
|
@@ -470,7 +470,7 @@ end
|
|
470
470
|
or
|
471
471
|
|
472
472
|
```ruby
|
473
|
-
class AddReferenceToUsers < ActiveRecord::Migration[7.
|
473
|
+
class AddReferenceToUsers < ActiveRecord::Migration[7.2]
|
474
474
|
def change
|
475
475
|
add_reference :users, :order, foreign_key: true
|
476
476
|
end
|
@@ -482,7 +482,7 @@ end
|
|
482
482
|
Add the foreign key without validating existing rows:
|
483
483
|
|
484
484
|
```ruby
|
485
|
-
class AddForeignKeyOnUsers < ActiveRecord::Migration[7.
|
485
|
+
class AddForeignKeyOnUsers < ActiveRecord::Migration[7.2]
|
486
486
|
def change
|
487
487
|
add_foreign_key :users, :orders, validate: false
|
488
488
|
end
|
@@ -492,7 +492,7 @@ end
|
|
492
492
|
Then validate them in a separate migration.
|
493
493
|
|
494
494
|
```ruby
|
495
|
-
class ValidateForeignKeyOnUsers < ActiveRecord::Migration[7.
|
495
|
+
class ValidateForeignKeyOnUsers < ActiveRecord::Migration[7.2]
|
496
496
|
def change
|
497
497
|
validate_foreign_key :users, :orders
|
498
498
|
end
|
@@ -506,7 +506,7 @@ end
|
|
506
506
|
In Postgres, adding a unique constraint creates a unique index, which blocks reads and writes.
|
507
507
|
|
508
508
|
```ruby
|
509
|
-
class AddUniqueConstraint < ActiveRecord::Migration[7.
|
509
|
+
class AddUniqueConstraint < ActiveRecord::Migration[7.2]
|
510
510
|
def change
|
511
511
|
add_unique_constraint :users, :some_column
|
512
512
|
end
|
@@ -518,7 +518,7 @@ end
|
|
518
518
|
Create a unique index concurrently, then use it for the constraint.
|
519
519
|
|
520
520
|
```ruby
|
521
|
-
class AddUniqueConstraint < ActiveRecord::Migration[7.
|
521
|
+
class AddUniqueConstraint < ActiveRecord::Migration[7.2]
|
522
522
|
disable_ddl_transaction!
|
523
523
|
|
524
524
|
def up
|
@@ -539,7 +539,7 @@ end
|
|
539
539
|
In Postgres, adding an exclusion constraint blocks reads and writes while every row is checked.
|
540
540
|
|
541
541
|
```ruby
|
542
|
-
class AddExclusionConstraint < ActiveRecord::Migration[7.
|
542
|
+
class AddExclusionConstraint < ActiveRecord::Migration[7.2]
|
543
543
|
def change
|
544
544
|
add_exclusion_constraint :users, "number WITH =", using: :gist
|
545
545
|
end
|
@@ -557,7 +557,7 @@ end
|
|
557
557
|
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
558
|
|
559
559
|
```ruby
|
560
|
-
class AddPropertiesToUsers < ActiveRecord::Migration[7.
|
560
|
+
class AddPropertiesToUsers < ActiveRecord::Migration[7.2]
|
561
561
|
def change
|
562
562
|
add_column :users, :properties, :json
|
563
563
|
end
|
@@ -569,7 +569,7 @@ end
|
|
569
569
|
Use `jsonb` instead.
|
570
570
|
|
571
571
|
```ruby
|
572
|
-
class AddPropertiesToUsers < ActiveRecord::Migration[7.
|
572
|
+
class AddPropertiesToUsers < ActiveRecord::Migration[7.2]
|
573
573
|
def change
|
574
574
|
add_column :users, :properties, :jsonb
|
575
575
|
end
|
@@ -585,7 +585,7 @@ end
|
|
585
585
|
In Postgres, setting `NOT NULL` on an existing column blocks reads and writes while every row is checked.
|
586
586
|
|
587
587
|
```ruby
|
588
|
-
class SetSomeColumnNotNull < ActiveRecord::Migration[7.
|
588
|
+
class SetSomeColumnNotNull < ActiveRecord::Migration[7.2]
|
589
589
|
def change
|
590
590
|
change_column_null :users, :some_column, false
|
591
591
|
end
|
@@ -597,7 +597,7 @@ end
|
|
597
597
|
Instead, add a check constraint.
|
598
598
|
|
599
599
|
```ruby
|
600
|
-
class SetSomeColumnNotNull < ActiveRecord::Migration[7.
|
600
|
+
class SetSomeColumnNotNull < ActiveRecord::Migration[7.2]
|
601
601
|
def change
|
602
602
|
add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
|
603
603
|
end
|
@@ -607,7 +607,7 @@ end
|
|
607
607
|
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
608
|
|
609
609
|
```ruby
|
610
|
-
class ValidateSomeColumnNotNull < ActiveRecord::Migration[7.
|
610
|
+
class ValidateSomeColumnNotNull < ActiveRecord::Migration[7.2]
|
611
611
|
def change
|
612
612
|
validate_check_constraint :users, name: "users_some_column_null"
|
613
613
|
change_column_null :users, :some_column, false
|
@@ -623,7 +623,7 @@ end
|
|
623
623
|
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
624
|
|
625
625
|
```ruby
|
626
|
-
class AddSomeColumnToUsers < ActiveRecord::Migration[7.
|
626
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[7.2]
|
627
627
|
def change
|
628
628
|
add_column :users, :some_column, :uuid, default: "gen_random_uuid()"
|
629
629
|
end
|
@@ -635,7 +635,7 @@ end
|
|
635
635
|
Instead, add the column without a default value, then change the default.
|
636
636
|
|
637
637
|
```ruby
|
638
|
-
class AddSomeColumnToUsers < ActiveRecord::Migration[7.
|
638
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[7.2]
|
639
639
|
def up
|
640
640
|
add_column :users, :some_column, :uuid
|
641
641
|
change_column_default :users, :some_column, from: nil, to: "gen_random_uuid()"
|
@@ -686,7 +686,7 @@ config.active_record.partial_inserts = false
|
|
686
686
|
Adding a non-unique index with more than three columns rarely improves performance.
|
687
687
|
|
688
688
|
```ruby
|
689
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[7.
|
689
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[7.2]
|
690
690
|
def change
|
691
691
|
add_index :users, [:a, :b, :c, :d]
|
692
692
|
end
|
@@ -698,7 +698,7 @@ end
|
|
698
698
|
Instead, start an index with columns that narrow down the results the most.
|
699
699
|
|
700
700
|
```ruby
|
701
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[7.
|
701
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[7.2]
|
702
702
|
def change
|
703
703
|
add_index :users, [:d, :b]
|
704
704
|
end
|
@@ -712,7 +712,7 @@ For Postgres, be sure to add them concurrently.
|
|
712
712
|
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
713
|
|
714
714
|
```ruby
|
715
|
-
class MySafeMigration < ActiveRecord::Migration[7.
|
715
|
+
class MySafeMigration < ActiveRecord::Migration[7.2]
|
716
716
|
def change
|
717
717
|
safety_assured { remove_column :users, :some_column }
|
718
718
|
end
|
@@ -934,15 +934,18 @@ You probably don’t need this gem for smaller projects, as operations that are
|
|
934
934
|
|
935
935
|
## Additional Reading
|
936
936
|
|
937
|
-
- [Rails Migrations with No Downtime](https://pedro.herokuapp.com/past/2011/7/13/rails_migrations_with_no_downtime/)
|
938
937
|
- [PostgreSQL at Scale: Database Schema Changes Without Downtime](https://medium.com/braintree-product-technology/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680)
|
939
|
-
- [
|
938
|
+
- [MySQL InnoDB Online DDL Operations](https://dev.mysql.com/doc/refman/en/innodb-online-ddl-operations.html)
|
940
939
|
- [MariaDB InnoDB Online DDL Overview](https://mariadb.com/kb/en/innodb-online-ddl-overview/)
|
941
940
|
|
942
941
|
## Credits
|
943
942
|
|
944
943
|
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
944
|
|
945
|
+
## History
|
946
|
+
|
947
|
+
View the [changelog](https://github.com/ankane/strong_migrations/blob/master/CHANGELOG.md)
|
948
|
+
|
946
949
|
## Contributing
|
947
950
|
|
948
951
|
Everyone is encouraged to help improve this project. Here are a few ways you can help:
|
@@ -17,5 +17,16 @@ module StrongMigrations
|
|
17
17
|
def columns(*args, **options)
|
18
18
|
@connection.columns(*args, **options).sort_by(&:name)
|
19
19
|
end
|
20
|
+
|
21
|
+
# forward private methods with send
|
22
|
+
# method_missing cannot tell how method was called
|
23
|
+
# this is not ideal, but other solutions have drawbacks
|
24
|
+
def send(name, ...)
|
25
|
+
if respond_to?(name, true)
|
26
|
+
super
|
27
|
+
else
|
28
|
+
@connection.send(name, ...)
|
29
|
+
end
|
30
|
+
end
|
20
31
|
end
|
21
32
|
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: 2.0.
|
4
|
+
version: 2.0.1
|
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: 2024-
|
13
|
+
date: 2024-10-15 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.5.
|
78
|
+
rubygems_version: 3.5.16
|
79
79
|
signing_key:
|
80
80
|
specification_version: 4
|
81
81
|
summary: Catch unsafe migrations in development
|