online_migrations 0.8.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +59 -59
- data/docs/configuring.md +1 -1
- data/lib/online_migrations/change_column_type_helpers.rb +8 -1
- data/lib/online_migrations/command_checker.rb +6 -1
- data/lib/online_migrations/migration.rb +9 -0
- data/lib/online_migrations/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3f7396712072756a2c7de52a7057f0c46da8ce26bf59270f0f2dea42bf43d0b3
|
4
|
+
data.tar.gz: a3a26397aa2a279e996e8af2e7c698ebabc73ee38bad89b84e98cbddb609feb4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d897c787f1b4e23436ecc362dd49a97eba529e7f0613fe71d38b4fde1cd740a40e0fa6ba2ef59d32f0ab6c1050ebc98728a1830d22679f98671305e1d82d6eb3
|
7
|
+
data.tar.gz: 10c2d6cc695042052c994111f71df5e19d17cf2b81a72b4ca2bce835ecced1a91dad0bfe5d2091079661c1d97dbbf066e641b80ceca196be7dd5c0e859f8ea09
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
## master (unreleased)
|
2
2
|
|
3
|
+
## 0.9.0 (2023-10-27)
|
4
|
+
|
5
|
+
- Add ability to use custom raw sql for `backfill_column_for_type_change`'s `type_cast_function`
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
backfill_column_for_type_change(:users, :company_id, type_cast_function: Arel.sql("company_id::integer"))
|
9
|
+
```
|
10
|
+
|
11
|
+
- Fix version safety with `revert`
|
12
|
+
|
3
13
|
## 0.8.2 (2023-09-26)
|
4
14
|
|
5
15
|
- Promote check constraint to `NOT NULL` on PostgreSQL >= 12 when changing column type
|
data/README.md
CHANGED
@@ -55,7 +55,7 @@ An operation is classified as dangerous if it either:
|
|
55
55
|
Consider the following migration:
|
56
56
|
|
57
57
|
```ruby
|
58
|
-
class AddAdminToUsers < ActiveRecord::Migration[7.
|
58
|
+
class AddAdminToUsers < ActiveRecord::Migration[7.1]
|
59
59
|
def change
|
60
60
|
add_column :users, :admin, :boolean, default: false, null: false
|
61
61
|
end
|
@@ -67,7 +67,7 @@ If the `users` table is large, running this migration on a live PostgreSQL < 11
|
|
67
67
|
A safer approach would be to run something like the following:
|
68
68
|
|
69
69
|
```ruby
|
70
|
-
class AddAdminToUsers < ActiveRecord::Migration[7.
|
70
|
+
class AddAdminToUsers < ActiveRecord::Migration[7.1]
|
71
71
|
# Do not wrap the migration in a transaction so that locks are held for a shorter time.
|
72
72
|
disable_ddl_transaction!
|
73
73
|
|
@@ -112,7 +112,7 @@ A safer approach is to:
|
|
112
112
|
|
113
113
|
add_column_with_default takes care of all this steps:
|
114
114
|
|
115
|
-
class AddAdminToUsers < ActiveRecord::Migration[7.
|
115
|
+
class AddAdminToUsers < ActiveRecord::Migration[7.1]
|
116
116
|
disable_ddl_transaction!
|
117
117
|
|
118
118
|
def change
|
@@ -166,7 +166,7 @@ You can also add [custom checks](#custom-checks) or [disable specific checks](#d
|
|
166
166
|
Active Record caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
|
167
167
|
|
168
168
|
```ruby
|
169
|
-
class RemoveNameFromUsers < ActiveRecord::Migration[7.
|
169
|
+
class RemoveNameFromUsers < ActiveRecord::Migration[7.1]
|
170
170
|
def change
|
171
171
|
remove_column :users, :name
|
172
172
|
end
|
@@ -195,7 +195,7 @@ end
|
|
195
195
|
3. Wrap column removing in a `safety_assured` block:
|
196
196
|
|
197
197
|
```ruby
|
198
|
-
class RemoveNameFromUsers < ActiveRecord::Migration[7.
|
198
|
+
class RemoveNameFromUsers < ActiveRecord::Migration[7.1]
|
199
199
|
def change
|
200
200
|
safety_assured { remove_column :users, :name }
|
201
201
|
end
|
@@ -212,7 +212,7 @@ end
|
|
212
212
|
In earlier versions of PostgreSQL adding a column with a non-null default value to an existing table blocks reads and writes while the entire table is rewritten.
|
213
213
|
|
214
214
|
```ruby
|
215
|
-
class AddAdminToUsers < ActiveRecord::Migration[7.
|
215
|
+
class AddAdminToUsers < ActiveRecord::Migration[7.1]
|
216
216
|
def change
|
217
217
|
add_column :users, :admin, :boolean, default: false
|
218
218
|
end
|
@@ -232,7 +232,7 @@ A safer approach is to:
|
|
232
232
|
`add_column_with_default` helper takes care of all this steps:
|
233
233
|
|
234
234
|
```ruby
|
235
|
-
class AddAdminToUsers < ActiveRecord::Migration[7.
|
235
|
+
class AddAdminToUsers < ActiveRecord::Migration[7.1]
|
236
236
|
disable_ddl_transaction!
|
237
237
|
|
238
238
|
def change
|
@@ -250,7 +250,7 @@ end
|
|
250
250
|
Active Record wraps each migration in a transaction, 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/).
|
251
251
|
|
252
252
|
```ruby
|
253
|
-
class AddAdminToUsers < ActiveRecord::Migration[7.
|
253
|
+
class AddAdminToUsers < ActiveRecord::Migration[7.1]
|
254
254
|
def change
|
255
255
|
add_column :users, :admin, :boolean
|
256
256
|
User.update_all(admin: false)
|
@@ -265,13 +265,13 @@ Also, running a single query to update data can cause issues for large tables.
|
|
265
265
|
There are three keys to backfilling safely: batching, throttling, and running it outside a transaction. Use a `update_column_in_batches` helper in a separate migration with `disable_ddl_transaction!`.
|
266
266
|
|
267
267
|
```ruby
|
268
|
-
class AddAdminToUsers < ActiveRecord::Migration[7.
|
268
|
+
class AddAdminToUsers < ActiveRecord::Migration[7.1]
|
269
269
|
def change
|
270
270
|
add_column :users, :admin, :boolean
|
271
271
|
end
|
272
272
|
end
|
273
273
|
|
274
|
-
class BackfillUsersAdminColumn < ActiveRecord::Migration[7.
|
274
|
+
class BackfillUsersAdminColumn < ActiveRecord::Migration[7.1]
|
275
275
|
disable_ddl_transaction!
|
276
276
|
|
277
277
|
def up
|
@@ -290,7 +290,7 @@ end
|
|
290
290
|
Changing the type of an existing column blocks reads and writes while the entire table is rewritten.
|
291
291
|
|
292
292
|
```ruby
|
293
|
-
class ChangeFilesSizeType < ActiveRecord::Migration[7.
|
293
|
+
class ChangeFilesSizeType < ActiveRecord::Migration[7.1]
|
294
294
|
def change
|
295
295
|
change_column :files, :size, :bigint
|
296
296
|
end
|
@@ -323,7 +323,7 @@ A safer approach can be accomplished in several steps:
|
|
323
323
|
1. Create a new column and keep column's data in sync:
|
324
324
|
|
325
325
|
```ruby
|
326
|
-
class InitializeChangeFilesSizeType < ActiveRecord::Migration[7.
|
326
|
+
class InitializeChangeFilesSizeType < ActiveRecord::Migration[7.1]
|
327
327
|
def change
|
328
328
|
initialize_column_type_change :files, :size, :bigint
|
329
329
|
end
|
@@ -336,7 +336,7 @@ which will be passed to `add_column` when creating a new column, so you can over
|
|
336
336
|
2. Backfill data from the old column to the new column:
|
337
337
|
|
338
338
|
```ruby
|
339
|
-
class BackfillChangeFilesSizeType < ActiveRecord::Migration[7.
|
339
|
+
class BackfillChangeFilesSizeType < ActiveRecord::Migration[7.1]
|
340
340
|
disable_ddl_transaction!
|
341
341
|
|
342
342
|
def up
|
@@ -352,7 +352,7 @@ which will be passed to `add_column` when creating a new column, so you can over
|
|
352
352
|
3. Copy indexes, foreign keys, check constraints, NOT NULL constraint, swap new column in place:
|
353
353
|
|
354
354
|
```ruby
|
355
|
-
class FinalizeChangeFilesSizeType < ActiveRecord::Migration[7.
|
355
|
+
class FinalizeChangeFilesSizeType < ActiveRecord::Migration[7.1]
|
356
356
|
disable_ddl_transaction!
|
357
357
|
|
358
358
|
def change
|
@@ -365,7 +365,7 @@ which will be passed to `add_column` when creating a new column, so you can over
|
|
365
365
|
5. Finally, if everything is working as expected, remove copy trigger and old column:
|
366
366
|
|
367
367
|
```ruby
|
368
|
-
class CleanupChangeFilesSizeType < ActiveRecord::Migration[7.
|
368
|
+
class CleanupChangeFilesSizeType < ActiveRecord::Migration[7.1]
|
369
369
|
def up
|
370
370
|
cleanup_column_type_change :files, :size
|
371
371
|
end
|
@@ -385,7 +385,7 @@ which will be passed to `add_column` when creating a new column, so you can over
|
|
385
385
|
Renaming a column that's in use will cause errors in your application.
|
386
386
|
|
387
387
|
```ruby
|
388
|
-
class RenameUsersNameToFirstName < ActiveRecord::Migration[7.
|
388
|
+
class RenameUsersNameToFirstName < ActiveRecord::Migration[7.1]
|
389
389
|
def change
|
390
390
|
rename_column :users, :name, :first_name
|
391
391
|
end
|
@@ -454,7 +454,7 @@ nor any data/indexes/foreign keys copying will be made, so will be instantaneous
|
|
454
454
|
It will use a combination of a VIEW and column aliasing to work with both column names simultaneously
|
455
455
|
|
456
456
|
```ruby
|
457
|
-
class InitializeRenameUsersNameToFirstName < ActiveRecord::Migration[7.
|
457
|
+
class InitializeRenameUsersNameToFirstName < ActiveRecord::Migration[7.1]
|
458
458
|
def change
|
459
459
|
initialize_column_rename :users, :name, :first_name
|
460
460
|
end
|
@@ -485,7 +485,7 @@ end
|
|
485
485
|
9. Remove the VIEW created in step 3 and finally rename the column:
|
486
486
|
|
487
487
|
```ruby
|
488
|
-
class FinalizeRenameUsersNameToFirstName < ActiveRecord::Migration[7.
|
488
|
+
class FinalizeRenameUsersNameToFirstName < ActiveRecord::Migration[7.1]
|
489
489
|
def change
|
490
490
|
finalize_column_rename :users, :name, :first_name
|
491
491
|
end
|
@@ -501,7 +501,7 @@ end
|
|
501
501
|
Renaming a table that's in use will cause errors in your application.
|
502
502
|
|
503
503
|
```ruby
|
504
|
-
class RenameClientsToUsers < ActiveRecord::Migration[7.
|
504
|
+
class RenameClientsToUsers < ActiveRecord::Migration[7.1]
|
505
505
|
def change
|
506
506
|
rename_table :clients, :users
|
507
507
|
end
|
@@ -556,7 +556,7 @@ OnlineMigrations.config.table_renames = {
|
|
556
556
|
3. Create a VIEW:
|
557
557
|
|
558
558
|
```ruby
|
559
|
-
class InitializeRenameClientsToUsers < ActiveRecord::Migration[7.
|
559
|
+
class InitializeRenameClientsToUsers < ActiveRecord::Migration[7.1]
|
560
560
|
def change
|
561
561
|
initialize_table_rename :clients, :users
|
562
562
|
end
|
@@ -569,7 +569,7 @@ end
|
|
569
569
|
7. Remove the VIEW created in step 3:
|
570
570
|
|
571
571
|
```ruby
|
572
|
-
class FinalizeRenameClientsToUsers < ActiveRecord::Migration[7.
|
572
|
+
class FinalizeRenameClientsToUsers < ActiveRecord::Migration[7.1]
|
573
573
|
def change
|
574
574
|
finalize_table_rename :clients, :users
|
575
575
|
end
|
@@ -585,7 +585,7 @@ end
|
|
585
585
|
The `force` option can drop an existing table.
|
586
586
|
|
587
587
|
```ruby
|
588
|
-
class CreateUsers < ActiveRecord::Migration[7.
|
588
|
+
class CreateUsers < ActiveRecord::Migration[7.1]
|
589
589
|
def change
|
590
590
|
create_table :users, force: true do |t|
|
591
591
|
# ...
|
@@ -599,7 +599,7 @@ end
|
|
599
599
|
Create tables without the `force` option.
|
600
600
|
|
601
601
|
```ruby
|
602
|
-
class CreateUsers < ActiveRecord::Migration[7.
|
602
|
+
class CreateUsers < ActiveRecord::Migration[7.1]
|
603
603
|
def change
|
604
604
|
create_table :users do |t|
|
605
605
|
# ...
|
@@ -617,7 +617,7 @@ If you intend to drop an existing table, run `drop_table` first.
|
|
617
617
|
Adding a check constraint blocks reads and writes while every row is checked.
|
618
618
|
|
619
619
|
```ruby
|
620
|
-
class AddCheckConstraint < ActiveRecord::Migration[7.
|
620
|
+
class AddCheckConstraint < ActiveRecord::Migration[7.1]
|
621
621
|
def change
|
622
622
|
add_check_constraint :users, "char_length(name) >= 1", name: "name_check"
|
623
623
|
end
|
@@ -629,7 +629,7 @@ end
|
|
629
629
|
Add the check constraint without validating existing rows, and then validate them in a separate transaction:
|
630
630
|
|
631
631
|
```ruby
|
632
|
-
class AddCheckConstraint < ActiveRecord::Migration[7.
|
632
|
+
class AddCheckConstraint < ActiveRecord::Migration[7.1]
|
633
633
|
disable_ddl_transaction!
|
634
634
|
|
635
635
|
def change
|
@@ -648,7 +648,7 @@ end
|
|
648
648
|
Setting `NOT NULL` on an existing column blocks reads and writes while every row is checked.
|
649
649
|
|
650
650
|
```ruby
|
651
|
-
class ChangeUsersNameNull < ActiveRecord::Migration[7.
|
651
|
+
class ChangeUsersNameNull < ActiveRecord::Migration[7.1]
|
652
652
|
def change
|
653
653
|
change_column_null :users, :name, false
|
654
654
|
end
|
@@ -660,7 +660,7 @@ end
|
|
660
660
|
Instead, add a check constraint and validate it in a separate transaction:
|
661
661
|
|
662
662
|
```ruby
|
663
|
-
class ChangeUsersNameNull < ActiveRecord::Migration[7.
|
663
|
+
class ChangeUsersNameNull < ActiveRecord::Migration[7.1]
|
664
664
|
disable_ddl_transaction!
|
665
665
|
|
666
666
|
def change
|
@@ -675,7 +675,7 @@ end
|
|
675
675
|
A `NOT NULL` check constraint is functionally equivalent to setting `NOT NULL` on the column (but it won't show up in `schema.rb` in Rails < 6.1). In PostgreSQL 12+, once the check constraint is validated, you can safely set `NOT NULL` on the column and drop the check constraint.
|
676
676
|
|
677
677
|
```ruby
|
678
|
-
class ChangeUsersNameNullDropCheck < ActiveRecord::Migration[7.
|
678
|
+
class ChangeUsersNameNullDropCheck < ActiveRecord::Migration[7.1]
|
679
679
|
def change
|
680
680
|
# in PostgreSQL 12+, you can then safely set NOT NULL on the column
|
681
681
|
change_column_null :users, :name, false
|
@@ -689,7 +689,7 @@ end
|
|
689
689
|
Online Migrations does not support inspecting what happens inside an `execute` call, so cannot help you here. Make really sure that what you're doing is safe before proceeding, then wrap it in a `safety_assured { ... }` block:
|
690
690
|
|
691
691
|
```ruby
|
692
|
-
class ExecuteSQL < ActiveRecord::Migration[7.
|
692
|
+
class ExecuteSQL < ActiveRecord::Migration[7.1]
|
693
693
|
def change
|
694
694
|
safety_assured { execute "..." }
|
695
695
|
end
|
@@ -703,7 +703,7 @@ end
|
|
703
703
|
Adding an index non-concurrently blocks writes.
|
704
704
|
|
705
705
|
```ruby
|
706
|
-
class AddIndexOnUsersEmail < ActiveRecord::Migration[7.
|
706
|
+
class AddIndexOnUsersEmail < ActiveRecord::Migration[7.1]
|
707
707
|
def change
|
708
708
|
add_index :users, :email, unique: true
|
709
709
|
end
|
@@ -715,7 +715,7 @@ end
|
|
715
715
|
Add indexes concurrently.
|
716
716
|
|
717
717
|
```ruby
|
718
|
-
class AddIndexOnUsersEmail < ActiveRecord::Migration[7.
|
718
|
+
class AddIndexOnUsersEmail < ActiveRecord::Migration[7.1]
|
719
719
|
disable_ddl_transaction!
|
720
720
|
|
721
721
|
def change
|
@@ -733,7 +733,7 @@ end
|
|
733
733
|
While actual removing of an index is usually fast, removing it non-concurrently tries to obtain an `ACCESS EXCLUSIVE` lock on the table, waiting for all existing queries to complete and blocking all the subsequent queries (even `SELECT`s) on that table until the lock is obtained and index is removed.
|
734
734
|
|
735
735
|
```ruby
|
736
|
-
class RemoveIndexOnUsersEmail < ActiveRecord::Migration[7.
|
736
|
+
class RemoveIndexOnUsersEmail < ActiveRecord::Migration[7.1]
|
737
737
|
def change
|
738
738
|
remove_index :users, :email
|
739
739
|
end
|
@@ -745,7 +745,7 @@ end
|
|
745
745
|
Remove indexes concurrently.
|
746
746
|
|
747
747
|
```ruby
|
748
|
-
class RemoveIndexOnUsersEmail < ActiveRecord::Migration[7.
|
748
|
+
class RemoveIndexOnUsersEmail < ActiveRecord::Migration[7.1]
|
749
749
|
disable_ddl_transaction!
|
750
750
|
|
751
751
|
def change
|
@@ -763,7 +763,7 @@ end
|
|
763
763
|
Removing an old index before replacing it with the new one might result in slow queries while building the new index.
|
764
764
|
|
765
765
|
```ruby
|
766
|
-
class AddIndexOnCreationToProjects < ActiveRecord::Migration[7.
|
766
|
+
class AddIndexOnCreationToProjects < ActiveRecord::Migration[7.1]
|
767
767
|
disable_ddl_transaction!
|
768
768
|
|
769
769
|
def change
|
@@ -780,7 +780,7 @@ end
|
|
780
780
|
A safer approach is to create the new index and then delete the old one.
|
781
781
|
|
782
782
|
```ruby
|
783
|
-
class AddIndexOnCreationToProjects < ActiveRecord::Migration[7.
|
783
|
+
class AddIndexOnCreationToProjects < ActiveRecord::Migration[7.1]
|
784
784
|
disable_ddl_transaction!
|
785
785
|
|
786
786
|
def change
|
@@ -797,7 +797,7 @@ end
|
|
797
797
|
Rails adds an index non-concurrently to references by default, which blocks writes. Additionally, if `foreign_key` option (without `validate: false`) is provided, both tables are blocked while it is validated.
|
798
798
|
|
799
799
|
```ruby
|
800
|
-
class AddUserToProjects < ActiveRecord::Migration[7.
|
800
|
+
class AddUserToProjects < ActiveRecord::Migration[7.1]
|
801
801
|
def change
|
802
802
|
add_reference :projects, :user, foreign_key: true
|
803
803
|
end
|
@@ -810,7 +810,7 @@ Make sure the index is added concurrently and the foreign key is added in a sepa
|
|
810
810
|
Or you can use `add_reference_concurrently` helper. It will create a reference and take care of safely adding index and/or foreign key.
|
811
811
|
|
812
812
|
```ruby
|
813
|
-
class AddUserToProjects < ActiveRecord::Migration[7.
|
813
|
+
class AddUserToProjects < ActiveRecord::Migration[7.1]
|
814
814
|
disable_ddl_transaction!
|
815
815
|
|
816
816
|
def change
|
@@ -828,7 +828,7 @@ end
|
|
828
828
|
Adding a foreign key blocks writes on both tables.
|
829
829
|
|
830
830
|
```ruby
|
831
|
-
class AddForeignKeyToProjectsUser < ActiveRecord::Migration[7.
|
831
|
+
class AddForeignKeyToProjectsUser < ActiveRecord::Migration[7.1]
|
832
832
|
def change
|
833
833
|
add_foreign_key :projects, :users
|
834
834
|
end
|
@@ -838,7 +838,7 @@ end
|
|
838
838
|
or
|
839
839
|
|
840
840
|
```ruby
|
841
|
-
class AddReferenceToProjectsUser < ActiveRecord::Migration[7.
|
841
|
+
class AddReferenceToProjectsUser < ActiveRecord::Migration[7.1]
|
842
842
|
def change
|
843
843
|
add_reference :projects, :user, foreign_key: true
|
844
844
|
end
|
@@ -850,7 +850,7 @@ end
|
|
850
850
|
Add the foreign key without validating existing rows, and then validate them in a separate transaction.
|
851
851
|
|
852
852
|
```ruby
|
853
|
-
class AddForeignKeyToProjectsUser < ActiveRecord::Migration[7.
|
853
|
+
class AddForeignKeyToProjectsUser < ActiveRecord::Migration[7.1]
|
854
854
|
disable_ddl_transaction!
|
855
855
|
|
856
856
|
def change
|
@@ -927,7 +927,7 @@ end
|
|
927
927
|
There's no equality operator for the `json` column type, which can cause errors for existing `SELECT DISTINCT` queries in your application.
|
928
928
|
|
929
929
|
```ruby
|
930
|
-
class AddSettingsToProjects < ActiveRecord::Migration[7.
|
930
|
+
class AddSettingsToProjects < ActiveRecord::Migration[7.1]
|
931
931
|
def change
|
932
932
|
add_column :projects, :settings, :json
|
933
933
|
end
|
@@ -939,7 +939,7 @@ end
|
|
939
939
|
Use `jsonb` instead.
|
940
940
|
|
941
941
|
```ruby
|
942
|
-
class AddSettingsToProjects < ActiveRecord::Migration[7.
|
942
|
+
class AddSettingsToProjects < ActiveRecord::Migration[7.1]
|
943
943
|
def change
|
944
944
|
add_column :projects, :settings, :jsonb
|
945
945
|
end
|
@@ -953,7 +953,7 @@ end
|
|
953
953
|
Adding a stored generated column causes the entire table to be rewritten. During this time, reads and writes are blocked.
|
954
954
|
|
955
955
|
```ruby
|
956
|
-
class AddLowerEmailToUsers < ActiveRecord::Migration[7.
|
956
|
+
class AddLowerEmailToUsers < ActiveRecord::Migration[7.1]
|
957
957
|
def change
|
958
958
|
add_column :users, :lower_email, :virtual, type: :string, as: "LOWER(email)", stored: true
|
959
959
|
end
|
@@ -971,7 +971,7 @@ Add a non-generated column and use callbacks or triggers instead.
|
|
971
971
|
When using short integer types as primary key types, [there is a risk](https://m.signalvnoise.com/update-on-basecamp-3-being-stuck-in-read-only-as-of-nov-8-922am-cst/) of running out of IDs on inserts. The default type in Active Record < 5.1 for primary and foreign keys is `INTEGER`, which allows a little over of 2 billion records. Active Record 5.1 changed the default type to `BIGINT`.
|
972
972
|
|
973
973
|
```ruby
|
974
|
-
class CreateUsers < ActiveRecord::Migration[7.
|
974
|
+
class CreateUsers < ActiveRecord::Migration[7.1]
|
975
975
|
def change
|
976
976
|
create_table :users, id: :integer do |t|
|
977
977
|
# ...
|
@@ -985,7 +985,7 @@ end
|
|
985
985
|
Use one of `bigint`, `bigserial`, `uuid` instead.
|
986
986
|
|
987
987
|
```ruby
|
988
|
-
class CreateUsers < ActiveRecord::Migration[7.
|
988
|
+
class CreateUsers < ActiveRecord::Migration[7.1]
|
989
989
|
def change
|
990
990
|
create_table :users, id: :bigint do |t| # bigint is the default for Active Record >= 5.1
|
991
991
|
# ...
|
@@ -1001,7 +1001,7 @@ end
|
|
1001
1001
|
Hash index operations are not WAL-logged, so hash indexes might need to be rebuilt with `REINDEX` after a database crash if there were unwritten changes. Also, changes to hash indexes are not replicated over streaming or file-based replication after the initial base backup, so they give wrong answers to queries that subsequently use them. For these reasons, hash index use is discouraged.
|
1002
1002
|
|
1003
1003
|
```ruby
|
1004
|
-
class AddIndexToUsersOnEmail < ActiveRecord::Migration[7.
|
1004
|
+
class AddIndexToUsersOnEmail < ActiveRecord::Migration[7.1]
|
1005
1005
|
def change
|
1006
1006
|
add_index :users, :email, unique: true, using: :hash
|
1007
1007
|
end
|
@@ -1013,7 +1013,7 @@ end
|
|
1013
1013
|
Use B-tree indexes instead.
|
1014
1014
|
|
1015
1015
|
```ruby
|
1016
|
-
class AddIndexToUsersOnEmail < ActiveRecord::Migration[7.
|
1016
|
+
class AddIndexToUsersOnEmail < ActiveRecord::Migration[7.1]
|
1017
1017
|
def change
|
1018
1018
|
add_index :users, :email, unique: true # B-tree by default
|
1019
1019
|
end
|
@@ -1028,7 +1028,7 @@ Adding multiple foreign keys in a single migration blocks reads and writes on al
|
|
1028
1028
|
Avoid adding foreign key more than once per migration file, unless the source and target tables are identical.
|
1029
1029
|
|
1030
1030
|
```ruby
|
1031
|
-
class CreateUserProjects < ActiveRecord::Migration[7.
|
1031
|
+
class CreateUserProjects < ActiveRecord::Migration[7.1]
|
1032
1032
|
def change
|
1033
1033
|
create_table :user_projects do |t|
|
1034
1034
|
t.belongs_to :user, foreign_key: true
|
@@ -1043,7 +1043,7 @@ end
|
|
1043
1043
|
Add additional foreign keys in separate migration files. See [adding a foreign key](#adding-a-foreign-key) for how to properly add foreign keys.
|
1044
1044
|
|
1045
1045
|
```ruby
|
1046
|
-
class CreateUserProjects < ActiveRecord::Migration[7.
|
1046
|
+
class CreateUserProjects < ActiveRecord::Migration[7.1]
|
1047
1047
|
def change
|
1048
1048
|
create_table :user_projects do |t|
|
1049
1049
|
t.belongs_to :user, foreign_key: true
|
@@ -1052,7 +1052,7 @@ class CreateUserProjects < ActiveRecord::Migration[7.0]
|
|
1052
1052
|
end
|
1053
1053
|
end
|
1054
1054
|
|
1055
|
-
class AddForeignKeyFromUserProjectsToProject < ActiveRecord::Migration[7.
|
1055
|
+
class AddForeignKeyFromUserProjectsToProject < ActiveRecord::Migration[7.1]
|
1056
1056
|
def change
|
1057
1057
|
add_foreign_key :user_projects, :projects
|
1058
1058
|
end
|
@@ -1069,7 +1069,7 @@ Remove all the foreign keys first.
|
|
1069
1069
|
Assuming, `projects` has foreign keys on `users.id` and `repositories.id`:
|
1070
1070
|
|
1071
1071
|
```ruby
|
1072
|
-
class DropProjects < ActiveRecord::Migration[7.
|
1072
|
+
class DropProjects < ActiveRecord::Migration[7.1]
|
1073
1073
|
def change
|
1074
1074
|
drop_table :projects
|
1075
1075
|
end
|
@@ -1081,13 +1081,13 @@ end
|
|
1081
1081
|
Remove all the foreign keys first:
|
1082
1082
|
|
1083
1083
|
```ruby
|
1084
|
-
class RemoveProjectsUserFk < ActiveRecord::Migration[7.
|
1084
|
+
class RemoveProjectsUserFk < ActiveRecord::Migration[7.1]
|
1085
1085
|
def change
|
1086
1086
|
remove_foreign_key :projects, :users
|
1087
1087
|
end
|
1088
1088
|
end
|
1089
1089
|
|
1090
|
-
class RemoveProjectsRepositoryFk < ActiveRecord::Migration[7.
|
1090
|
+
class RemoveProjectsRepositoryFk < ActiveRecord::Migration[7.1]
|
1091
1091
|
def change
|
1092
1092
|
remove_foreign_key :projects, :repositories
|
1093
1093
|
end
|
@@ -1097,7 +1097,7 @@ end
|
|
1097
1097
|
Then remove the table:
|
1098
1098
|
|
1099
1099
|
```ruby
|
1100
|
-
class DropProjects < ActiveRecord::Migration[7.
|
1100
|
+
class DropProjects < ActiveRecord::Migration[7.1]
|
1101
1101
|
def change
|
1102
1102
|
drop_table :projects
|
1103
1103
|
end
|
@@ -1114,7 +1114,7 @@ Otherwise, there's a risk of bugs caused by IDs representable by one type but no
|
|
1114
1114
|
Assuming, there is a `users` table with `bigint` primary key type:
|
1115
1115
|
|
1116
1116
|
```ruby
|
1117
|
-
class AddUserIdToProjects < ActiveRecord::Migration[7.
|
1117
|
+
class AddUserIdToProjects < ActiveRecord::Migration[7.1]
|
1118
1118
|
def change
|
1119
1119
|
add_column :projects, :user_id, :integer
|
1120
1120
|
end
|
@@ -1128,7 +1128,7 @@ Add a reference column of the same type as a referenced primary key.
|
|
1128
1128
|
Assuming, there is a `users` table with `bigint` primary key type:
|
1129
1129
|
|
1130
1130
|
```ruby
|
1131
|
-
class AddUserIdToProjects < ActiveRecord::Migration[7.
|
1131
|
+
class AddUserIdToProjects < ActiveRecord::Migration[7.1]
|
1132
1132
|
def change
|
1133
1133
|
add_column :projects, :user_id, :bigint
|
1134
1134
|
end
|
@@ -1142,7 +1142,7 @@ end
|
|
1142
1142
|
Adding a single table inheritance column might cause errors in old instances of your application.
|
1143
1143
|
|
1144
1144
|
```ruby
|
1145
|
-
class AddTypeToUsers < ActiveRecord::Migration[7.
|
1145
|
+
class AddTypeToUsers < ActiveRecord::Migration[7.1]
|
1146
1146
|
def change
|
1147
1147
|
add_column :users, :string, :type, default: "Member"
|
1148
1148
|
end
|
@@ -1182,7 +1182,7 @@ A safer approach is to:
|
|
1182
1182
|
Active Record < 7 enables partial writes by default, which can cause incorrect values to be inserted when changing the default value of a column.
|
1183
1183
|
|
1184
1184
|
```ruby
|
1185
|
-
class ChangeSomeColumnDefault < ActiveRecord::Migration[7.
|
1185
|
+
class ChangeSomeColumnDefault < ActiveRecord::Migration[7.1]
|
1186
1186
|
def change
|
1187
1187
|
change_column_default :users, :some_column, from: "old", to: "new"
|
1188
1188
|
end
|
@@ -1210,7 +1210,7 @@ config.active_record.partial_inserts = false
|
|
1210
1210
|
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.
|
1211
1211
|
|
1212
1212
|
```ruby
|
1213
|
-
class MySafeMigration < ActiveRecord::Migration[7.
|
1213
|
+
class MySafeMigration < ActiveRecord::Migration[7.1]
|
1214
1214
|
def change
|
1215
1215
|
safety_assured { remove_column :users, :some_column }
|
1216
1216
|
end
|
data/docs/configuring.md
CHANGED
@@ -154,7 +154,7 @@ This is useful to demystify `online_migrations` inner workings, and to better in
|
|
154
154
|
Consider migration, running on PostgreSQL < 11:
|
155
155
|
|
156
156
|
```ruby
|
157
|
-
class AddAdminToUsers < ActiveRecord::Migration[7.
|
157
|
+
class AddAdminToUsers < ActiveRecord::Migration[7.1]
|
158
158
|
disable_ddl_transaction!
|
159
159
|
|
160
160
|
def change
|
@@ -176,6 +176,7 @@ module OnlineMigrations
|
|
176
176
|
#
|
177
177
|
# @example With type casting
|
178
178
|
# backfill_column_for_type_change(:users, :settings, type_cast_function: "jsonb")
|
179
|
+
# backfill_column_for_type_change(:users, :company_id, type_cast_function: Arel.sql("company_id::integer"))
|
179
180
|
#
|
180
181
|
# @example Additional batch options
|
181
182
|
# backfill_column_for_type_change(:files, :size, batch_size: 10_000)
|
@@ -202,7 +203,13 @@ module OnlineMigrations
|
|
202
203
|
|
203
204
|
old_value = Arel::Table.new(table_name)[column_name]
|
204
205
|
if (type_cast_function = type_cast_functions.with_indifferent_access[column_name])
|
205
|
-
old_value =
|
206
|
+
old_value =
|
207
|
+
case type_cast_function
|
208
|
+
when Arel::Nodes::SqlLiteral
|
209
|
+
type_cast_function
|
210
|
+
else
|
211
|
+
Arel::Nodes::NamedFunction.new(type_cast_function.to_s, [old_value])
|
212
|
+
end
|
206
213
|
end
|
207
214
|
|
208
215
|
[tmp_column, old_value]
|
@@ -48,6 +48,10 @@ module OnlineMigrations
|
|
48
48
|
end
|
49
49
|
ruby2_keywords(:check) if respond_to?(:ruby2_keywords, true)
|
50
50
|
|
51
|
+
def version_safe?
|
52
|
+
version && version <= OnlineMigrations.config.start_after
|
53
|
+
end
|
54
|
+
|
51
55
|
private
|
52
56
|
def check_database_version
|
53
57
|
return if defined?(@database_version_checked)
|
@@ -107,7 +111,8 @@ module OnlineMigrations
|
|
107
111
|
self.class.safe ||
|
108
112
|
ENV["SAFETY_ASSURED"] ||
|
109
113
|
(direction == :down && !OnlineMigrations.config.check_down) ||
|
110
|
-
|
114
|
+
version_safe? ||
|
115
|
+
@migration.reverting?
|
111
116
|
end
|
112
117
|
|
113
118
|
def version
|
@@ -30,6 +30,15 @@ module OnlineMigrations
|
|
30
30
|
end
|
31
31
|
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
32
32
|
|
33
|
+
# @private
|
34
|
+
def revert(*args)
|
35
|
+
if command_checker.version_safe?
|
36
|
+
safety_assured { super }
|
37
|
+
else
|
38
|
+
super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
33
42
|
# Mark a command in the migration as safe, despite using a method that might otherwise be dangerous.
|
34
43
|
#
|
35
44
|
# @example
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: online_migrations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- fatkodima
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|