strong_migrations 2.1.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/LICENSE.txt +1 -1
- data/README.md +40 -40
- data/lib/strong_migrations/adapters/abstract_adapter.rb +3 -0
- data/lib/strong_migrations/adapters/mysql_adapter.rb +4 -0
- data/lib/strong_migrations/adapters/postgresql_adapter.rb +7 -9
- data/lib/strong_migrations/checks.rb +16 -6
- data/lib/strong_migrations/safe_methods.rb +30 -3
- data/lib/strong_migrations/version.rb +1 -1
- metadata +5 -9
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: a78b56711a718210a3d14c82a4c30734c0b653827c1a2bdcc7617c27a7eed0e5
         | 
| 4 | 
            +
              data.tar.gz: 9ef73e9cd8783a34d330c3517d98df50266427bd10bd92656cc528753aceb045
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 0ece935c993dad2d316d183be938db27a9595cac2d0880f5fcd3f516965440bda73da9e8ad5239269451b991b04a897de03b8f0ed11b30400074daf30fa353b7
         | 
| 7 | 
            +
              data.tar.gz: c8a18a098a6356daae4bf6bb827fa7f11c0a37e8b88712083af88f4eeb0f8dcda7f893491f1a044cc2399afc8147c31c3b07bf46a2155b972c78bb2aebbd479e
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,3 +1,14 @@ | |
| 1 | 
            +
            ## 2.2.1 (2025-03-21)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            - Added support for `change_column_null` with default value with `safe_by_default` option
         | 
| 4 | 
            +
            - Improved backfill instructions
         | 
| 5 | 
            +
            - Fixed `safe_by_default` applying to migrations before `start_after`
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            ## 2.2.0 (2025-02-01)
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            - Fixed constraint name for long table and column names with `change_column_null`
         | 
| 10 | 
            +
            - Dropped support for Active Record < 7
         | 
| 11 | 
            +
             | 
| 1 12 | 
             
            ## 2.1.0 (2024-11-08)
         | 
| 2 13 |  | 
| 3 14 | 
             
            - Added `skip_database` method
         | 
    
        data/LICENSE.txt
    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[ | 
| 46 | 
            +
            class RemoveColumn < ActiveRecord::Migration[8.0]
         | 
| 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[ | 
| 101 | 
            +
            class RemoveSomeColumnFromUsers < ActiveRecord::Migration[8.0]
         | 
| 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[ | 
| 122 | 
            +
              class RemoveSomeColumnFromUsers < ActiveRecord::Migration[8.0]
         | 
| 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[ | 
| 139 | 
            +
            class ChangeSomeColumnType < ActiveRecord::Migration[8.0]
         | 
| 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[ | 
| 185 | 
            +
            class RenameSomeColumn < ActiveRecord::Migration[8.0]
         | 
| 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[ | 
| 210 | 
            +
            class RenameUsersToCustomers < ActiveRecord::Migration[8.0]
         | 
| 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[ | 
| 235 | 
            +
            class CreateUsers < ActiveRecord::Migration[8.0]
         | 
| 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[ | 
| 249 | 
            +
            class CreateUsers < ActiveRecord::Migration[8.0]
         | 
| 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[ | 
| 267 | 
            +
            class AddIdToCitiesUsers < ActiveRecord::Migration[8.0]
         | 
| 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[ | 
| 287 | 
            +
            class AddSomeColumnToUsers < ActiveRecord::Migration[8.0]
         | 
| 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[ | 
| 307 | 
            +
            class AddCheckConstraint < ActiveRecord::Migration[8.0]
         | 
| 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[ | 
| 319 | 
            +
            class AddCheckConstraint < ActiveRecord::Migration[8.0]
         | 
| 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[ | 
| 329 | 
            +
            class ValidateCheckConstraint < ActiveRecord::Migration[8.0]
         | 
| 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[ | 
| 345 | 
            +
            class ExecuteSQL < ActiveRecord::Migration[8.0]
         | 
| 346 346 | 
             
              def change
         | 
| 347 347 | 
             
                safety_assured { execute "..." }
         | 
| 348 348 | 
             
              end
         | 
| @@ -358,7 +358,7 @@ Note: Strong Migrations does not detect dangerous backfills. | |
| 358 358 | 
             
            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/).
         | 
| 359 359 |  | 
| 360 360 | 
             
            ```ruby
         | 
| 361 | 
            -
            class AddSomeColumnToUsers < ActiveRecord::Migration[ | 
| 361 | 
            +
            class AddSomeColumnToUsers < ActiveRecord::Migration[8.0]
         | 
| 362 362 | 
             
              def change
         | 
| 363 363 | 
             
                add_column :users, :some_column, :text
         | 
| 364 364 | 
             
                User.update_all some_column: "default_value"
         | 
| @@ -373,12 +373,12 @@ Also, running a single query to update data can cause issues for large tables. | |
| 373 373 | 
             
            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!`.
         | 
| 374 374 |  | 
| 375 375 | 
             
            ```ruby
         | 
| 376 | 
            -
            class BackfillSomeColumn < ActiveRecord::Migration[ | 
| 376 | 
            +
            class BackfillSomeColumn < ActiveRecord::Migration[8.0]
         | 
| 377 377 | 
             
              disable_ddl_transaction!
         | 
| 378 378 |  | 
| 379 379 | 
             
              def up
         | 
| 380 | 
            -
                User.unscoped.in_batches do |relation|
         | 
| 381 | 
            -
                  relation.update_all some_column: "default_value"
         | 
| 380 | 
            +
                User.unscoped.in_batches(of: 10000) do |relation|
         | 
| 381 | 
            +
                  relation.where(some_column: nil).update_all some_column: "default_value"
         | 
| 382 382 | 
             
                  sleep(0.01) # throttle
         | 
| 383 383 | 
             
                end
         | 
| 384 384 | 
             
              end
         | 
| @@ -396,7 +396,7 @@ Note: If backfilling with a method other than `update_all`, use `User.reset_colu | |
| 396 396 | 
             
            In Postgres, adding an index non-concurrently blocks writes.
         | 
| 397 397 |  | 
| 398 398 | 
             
            ```ruby
         | 
| 399 | 
            -
            class AddSomeIndexToUsers < ActiveRecord::Migration[ | 
| 399 | 
            +
            class AddSomeIndexToUsers < ActiveRecord::Migration[8.0]
         | 
| 400 400 | 
             
              def change
         | 
| 401 401 | 
             
                add_index :users, :some_column
         | 
| 402 402 | 
             
              end
         | 
| @@ -408,7 +408,7 @@ end | |
| 408 408 | 
             
            Add indexes concurrently.
         | 
| 409 409 |  | 
| 410 410 | 
             
            ```ruby
         | 
| 411 | 
            -
            class AddSomeIndexToUsers < ActiveRecord::Migration[ | 
| 411 | 
            +
            class AddSomeIndexToUsers < ActiveRecord::Migration[8.0]
         | 
| 412 412 | 
             
              disable_ddl_transaction!
         | 
| 413 413 |  | 
| 414 414 | 
             
              def change
         | 
| @@ -434,7 +434,7 @@ rails g index table column | |
| 434 434 | 
             
            Rails adds an index non-concurrently to references by default, which blocks writes in Postgres.
         | 
| 435 435 |  | 
| 436 436 | 
             
            ```ruby
         | 
| 437 | 
            -
            class AddReferenceToUsers < ActiveRecord::Migration[ | 
| 437 | 
            +
            class AddReferenceToUsers < ActiveRecord::Migration[8.0]
         | 
| 438 438 | 
             
              def change
         | 
| 439 439 | 
             
                add_reference :users, :city
         | 
| 440 440 | 
             
              end
         | 
| @@ -446,7 +446,7 @@ end | |
| 446 446 | 
             
            Make sure the index is added concurrently.
         | 
| 447 447 |  | 
| 448 448 | 
             
            ```ruby
         | 
| 449 | 
            -
            class AddReferenceToUsers < ActiveRecord::Migration[ | 
| 449 | 
            +
            class AddReferenceToUsers < ActiveRecord::Migration[8.0]
         | 
| 450 450 | 
             
              disable_ddl_transaction!
         | 
| 451 451 |  | 
| 452 452 | 
             
              def change
         | 
| @@ -464,7 +464,7 @@ end | |
| 464 464 | 
             
            In Postgres, adding a foreign key blocks writes on both tables.
         | 
| 465 465 |  | 
| 466 466 | 
             
            ```ruby
         | 
| 467 | 
            -
            class AddForeignKeyOnUsers < ActiveRecord::Migration[ | 
| 467 | 
            +
            class AddForeignKeyOnUsers < ActiveRecord::Migration[8.0]
         | 
| 468 468 | 
             
              def change
         | 
| 469 469 | 
             
                add_foreign_key :users, :orders
         | 
| 470 470 | 
             
              end
         | 
| @@ -474,7 +474,7 @@ end | |
| 474 474 | 
             
            or
         | 
| 475 475 |  | 
| 476 476 | 
             
            ```ruby
         | 
| 477 | 
            -
            class AddReferenceToUsers < ActiveRecord::Migration[ | 
| 477 | 
            +
            class AddReferenceToUsers < ActiveRecord::Migration[8.0]
         | 
| 478 478 | 
             
              def change
         | 
| 479 479 | 
             
                add_reference :users, :order, foreign_key: true
         | 
| 480 480 | 
             
              end
         | 
| @@ -486,7 +486,7 @@ end | |
| 486 486 | 
             
            Add the foreign key without validating existing rows:
         | 
| 487 487 |  | 
| 488 488 | 
             
            ```ruby
         | 
| 489 | 
            -
            class AddForeignKeyOnUsers < ActiveRecord::Migration[ | 
| 489 | 
            +
            class AddForeignKeyOnUsers < ActiveRecord::Migration[8.0]
         | 
| 490 490 | 
             
              def change
         | 
| 491 491 | 
             
                add_foreign_key :users, :orders, validate: false
         | 
| 492 492 | 
             
              end
         | 
| @@ -496,7 +496,7 @@ end | |
| 496 496 | 
             
            Then validate them in a separate migration.
         | 
| 497 497 |  | 
| 498 498 | 
             
            ```ruby
         | 
| 499 | 
            -
            class ValidateForeignKeyOnUsers < ActiveRecord::Migration[ | 
| 499 | 
            +
            class ValidateForeignKeyOnUsers < ActiveRecord::Migration[8.0]
         | 
| 500 500 | 
             
              def change
         | 
| 501 501 | 
             
                validate_foreign_key :users, :orders
         | 
| 502 502 | 
             
              end
         | 
| @@ -510,7 +510,7 @@ end | |
| 510 510 | 
             
            In Postgres, adding a unique constraint creates a unique index, which blocks reads and writes.
         | 
| 511 511 |  | 
| 512 512 | 
             
            ```ruby
         | 
| 513 | 
            -
            class AddUniqueConstraint < ActiveRecord::Migration[ | 
| 513 | 
            +
            class AddUniqueConstraint < ActiveRecord::Migration[8.0]
         | 
| 514 514 | 
             
              def change
         | 
| 515 515 | 
             
                add_unique_constraint :users, :some_column
         | 
| 516 516 | 
             
              end
         | 
| @@ -522,7 +522,7 @@ end | |
| 522 522 | 
             
            Create a unique index concurrently, then use it for the constraint.
         | 
| 523 523 |  | 
| 524 524 | 
             
            ```ruby
         | 
| 525 | 
            -
            class AddUniqueConstraint < ActiveRecord::Migration[ | 
| 525 | 
            +
            class AddUniqueConstraint < ActiveRecord::Migration[8.0]
         | 
| 526 526 | 
             
              disable_ddl_transaction!
         | 
| 527 527 |  | 
| 528 528 | 
             
              def up
         | 
| @@ -543,7 +543,7 @@ end | |
| 543 543 | 
             
            In Postgres, adding an exclusion constraint blocks reads and writes while every row is checked.
         | 
| 544 544 |  | 
| 545 545 | 
             
            ```ruby
         | 
| 546 | 
            -
            class AddExclusionConstraint < ActiveRecord::Migration[ | 
| 546 | 
            +
            class AddExclusionConstraint < ActiveRecord::Migration[8.0]
         | 
| 547 547 | 
             
              def change
         | 
| 548 548 | 
             
                add_exclusion_constraint :users, "number WITH =", using: :gist
         | 
| 549 549 | 
             
              end
         | 
| @@ -561,7 +561,7 @@ end | |
| 561 561 | 
             
            In Postgres, there’s no equality operator for the `json` column type, which can cause errors for existing `SELECT DISTINCT` queries in your application.
         | 
| 562 562 |  | 
| 563 563 | 
             
            ```ruby
         | 
| 564 | 
            -
            class AddPropertiesToUsers < ActiveRecord::Migration[ | 
| 564 | 
            +
            class AddPropertiesToUsers < ActiveRecord::Migration[8.0]
         | 
| 565 565 | 
             
              def change
         | 
| 566 566 | 
             
                add_column :users, :properties, :json
         | 
| 567 567 | 
             
              end
         | 
| @@ -573,7 +573,7 @@ end | |
| 573 573 | 
             
            Use `jsonb` instead.
         | 
| 574 574 |  | 
| 575 575 | 
             
            ```ruby
         | 
| 576 | 
            -
            class AddPropertiesToUsers < ActiveRecord::Migration[ | 
| 576 | 
            +
            class AddPropertiesToUsers < ActiveRecord::Migration[8.0]
         | 
| 577 577 | 
             
              def change
         | 
| 578 578 | 
             
                add_column :users, :properties, :jsonb
         | 
| 579 579 | 
             
              end
         | 
| @@ -589,7 +589,7 @@ end | |
| 589 589 | 
             
            In Postgres, setting `NOT NULL` on an existing column blocks reads and writes while every row is checked.
         | 
| 590 590 |  | 
| 591 591 | 
             
            ```ruby
         | 
| 592 | 
            -
            class SetSomeColumnNotNull < ActiveRecord::Migration[ | 
| 592 | 
            +
            class SetSomeColumnNotNull < ActiveRecord::Migration[8.0]
         | 
| 593 593 | 
             
              def change
         | 
| 594 594 | 
             
                change_column_null :users, :some_column, false
         | 
| 595 595 | 
             
              end
         | 
| @@ -601,7 +601,7 @@ end | |
| 601 601 | 
             
            Instead, add a check constraint.
         | 
| 602 602 |  | 
| 603 603 | 
             
            ```ruby
         | 
| 604 | 
            -
            class SetSomeColumnNotNull < ActiveRecord::Migration[ | 
| 604 | 
            +
            class SetSomeColumnNotNull < ActiveRecord::Migration[8.0]
         | 
| 605 605 | 
             
              def change
         | 
| 606 606 | 
             
                add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
         | 
| 607 607 | 
             
              end
         | 
| @@ -611,7 +611,7 @@ end | |
| 611 611 | 
             
            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.
         | 
| 612 612 |  | 
| 613 613 | 
             
            ```ruby
         | 
| 614 | 
            -
            class ValidateSomeColumnNotNull < ActiveRecord::Migration[ | 
| 614 | 
            +
            class ValidateSomeColumnNotNull < ActiveRecord::Migration[8.0]
         | 
| 615 615 | 
             
              def up
         | 
| 616 616 | 
             
                validate_check_constraint :users, name: "users_some_column_null"
         | 
| 617 617 | 
             
                change_column_null :users, :some_column, false
         | 
| @@ -632,7 +632,7 @@ end | |
| 632 632 | 
             
            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.
         | 
| 633 633 |  | 
| 634 634 | 
             
            ```ruby
         | 
| 635 | 
            -
            class AddSomeColumnToUsers < ActiveRecord::Migration[ | 
| 635 | 
            +
            class AddSomeColumnToUsers < ActiveRecord::Migration[8.0]
         | 
| 636 636 | 
             
              def change
         | 
| 637 637 | 
             
                add_column :users, :some_column, :uuid, default: "gen_random_uuid()"
         | 
| 638 638 | 
             
              end
         | 
| @@ -644,7 +644,7 @@ end | |
| 644 644 | 
             
            Instead, add the column without a default value, then change the default.
         | 
| 645 645 |  | 
| 646 646 | 
             
            ```ruby
         | 
| 647 | 
            -
            class AddSomeColumnToUsers < ActiveRecord::Migration[ | 
| 647 | 
            +
            class AddSomeColumnToUsers < ActiveRecord::Migration[8.0]
         | 
| 648 648 | 
             
              def up
         | 
| 649 649 | 
             
                add_column :users, :some_column, :uuid
         | 
| 650 650 | 
             
                change_column_default :users, :some_column, from: nil, to: "gen_random_uuid()"
         | 
| @@ -695,7 +695,7 @@ config.active_record.partial_inserts = false | |
| 695 695 | 
             
            Adding a non-unique index with more than three columns rarely improves performance.
         | 
| 696 696 |  | 
| 697 697 | 
             
            ```ruby
         | 
| 698 | 
            -
            class AddSomeIndexToUsers < ActiveRecord::Migration[ | 
| 698 | 
            +
            class AddSomeIndexToUsers < ActiveRecord::Migration[8.0]
         | 
| 699 699 | 
             
              def change
         | 
| 700 700 | 
             
                add_index :users, [:a, :b, :c, :d]
         | 
| 701 701 | 
             
              end
         | 
| @@ -707,7 +707,7 @@ end | |
| 707 707 | 
             
            Instead, start an index with columns that narrow down the results the most.
         | 
| 708 708 |  | 
| 709 709 | 
             
            ```ruby
         | 
| 710 | 
            -
            class AddSomeIndexToUsers < ActiveRecord::Migration[ | 
| 710 | 
            +
            class AddSomeIndexToUsers < ActiveRecord::Migration[8.0]
         | 
| 711 711 | 
             
              def change
         | 
| 712 712 | 
             
                add_index :users, [:d, :b]
         | 
| 713 713 | 
             
              end
         | 
| @@ -721,7 +721,7 @@ For Postgres, be sure to add them concurrently. | |
| 721 721 | 
             
            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.
         | 
| 722 722 |  | 
| 723 723 | 
             
            ```ruby
         | 
| 724 | 
            -
            class MySafeMigration < ActiveRecord::Migration[ | 
| 724 | 
            +
            class MySafeMigration < ActiveRecord::Migration[8.0]
         | 
| 725 725 | 
             
              def change
         | 
| 726 726 | 
             
                safety_assured { remove_column :users, :some_column }
         | 
| 727 727 | 
             
              end
         | 
| @@ -910,7 +910,7 @@ StrongMigrations.lock_timeout_retry_delay = 10.seconds | |
| 910 910 | 
             
            To mark migrations as safe that were created before installing this gem, create an initializer with:
         | 
| 911 911 |  | 
| 912 912 | 
             
            ```ruby
         | 
| 913 | 
            -
            StrongMigrations.start_after =  | 
| 913 | 
            +
            StrongMigrations.start_after = 20250101000000
         | 
| 914 914 | 
             
            ```
         | 
| 915 915 |  | 
| 916 916 | 
             
            Use the version from your latest migration.
         | 
| @@ -173,6 +173,10 @@ module StrongMigrations | |
| 173 173 | 
             
                    ["primary_key", "serial", "bigserial"]
         | 
| 174 174 | 
             
                  end
         | 
| 175 175 |  | 
| 176 | 
            +
                  def max_constraint_name_length
         | 
| 177 | 
            +
                    63
         | 
| 178 | 
            +
                  end
         | 
| 179 | 
            +
             | 
| 176 180 | 
             
                  private
         | 
| 177 181 |  | 
| 178 182 | 
             
                  def set_timeout(setting, timeout)
         | 
| @@ -210,15 +214,9 @@ module StrongMigrations | |
| 210 214 | 
             
                  end
         | 
| 211 215 |  | 
| 212 216 | 
             
                  def datetime_type
         | 
| 213 | 
            -
                     | 
| 214 | 
            -
             | 
| 215 | 
            -
             | 
| 216 | 
            -
                        # no need to support custom datetime_types
         | 
| 217 | 
            -
                        connection.class.datetime_type
         | 
| 218 | 
            -
                      else
         | 
| 219 | 
            -
                        # https://github.com/rails/rails/issues/21126#issuecomment-327895275
         | 
| 220 | 
            -
                        :datetime
         | 
| 221 | 
            -
                      end
         | 
| 217 | 
            +
                    # https://github.com/rails/rails/pull/41084
         | 
| 218 | 
            +
                    # no need to support custom datetime_types
         | 
| 219 | 
            +
                    key = connection.class.datetime_type
         | 
| 222 220 |  | 
| 223 221 | 
             
                    # could be timestamp, timestamp without time zone, timestamp with time zone, etc
         | 
| 224 222 | 
             
                    connection.class.const_get(:NATIVE_DATABASE_TYPES).fetch(key).fetch(:name)
         | 
| @@ -216,11 +216,11 @@ module StrongMigrations | |
| 216 216 | 
             
                  table, column, _default_or_changes = args
         | 
| 217 217 |  | 
| 218 218 | 
             
                  # just check ActiveRecord::Base, even though can override on model
         | 
| 219 | 
            -
                  partial_inserts =  | 
| 219 | 
            +
                  partial_inserts = ActiveRecord::Base.partial_inserts
         | 
| 220 220 |  | 
| 221 221 | 
             
                  if partial_inserts && !new_column?(table, column)
         | 
| 222 222 | 
             
                    raise_error :change_column_default,
         | 
| 223 | 
            -
                      config:  | 
| 223 | 
            +
                      config: "partial_inserts"
         | 
| 224 224 | 
             
                  end
         | 
| 225 225 | 
             
                end
         | 
| 226 226 |  | 
| @@ -232,16 +232,26 @@ module StrongMigrations | |
| 232 232 | 
             
                      safe = constraints.any? { |c| c.options[:validate] && (c.expression == "#{column} IS NOT NULL" || c.expression == "#{connection.quote_column_name(column)} IS NOT NULL") }
         | 
| 233 233 |  | 
| 234 234 | 
             
                      unless safe
         | 
| 235 | 
            +
                        expression = "#{quote_column_if_needed(column)} IS NOT NULL"
         | 
| 236 | 
            +
             | 
| 235 237 | 
             
                        # match https://github.com/nullobject/rein
         | 
| 236 238 | 
             
                        constraint_name = "#{table}_#{column}_null"
         | 
| 239 | 
            +
                        if adapter.max_constraint_name_length && constraint_name.bytesize > adapter.max_constraint_name_length
         | 
| 240 | 
            +
                          constraint_name = connection.check_constraint_options(table, expression, {})[:name]
         | 
| 241 | 
            +
             | 
| 242 | 
            +
                          # avoid collision with Active Record naming for safe_by_default
         | 
| 243 | 
            +
                          if StrongMigrations.safe_by_default
         | 
| 244 | 
            +
                            constraint_name = constraint_name.sub("rails", "strong_migrations")
         | 
| 245 | 
            +
                          end
         | 
| 246 | 
            +
                        end
         | 
| 237 247 |  | 
| 238 | 
            -
                        add_args = [table,  | 
| 248 | 
            +
                        add_args = [table, expression, {name: constraint_name, validate: false}]
         | 
| 239 249 | 
             
                        validate_args = [table, {name: constraint_name}]
         | 
| 240 250 | 
             
                        change_args = [table, column, null]
         | 
| 241 251 | 
             
                        remove_args = [table, {name: constraint_name}]
         | 
| 242 252 |  | 
| 243 253 | 
             
                        if StrongMigrations.safe_by_default
         | 
| 244 | 
            -
                          safe_change_column_null(add_args, validate_args, change_args, remove_args, default, constraints)
         | 
| 254 | 
            +
                          safe_change_column_null(add_args, validate_args, change_args, remove_args, table, column, default, constraints)
         | 
| 245 255 | 
             
                          throw :safe
         | 
| 246 256 | 
             
                        end
         | 
| 247 257 |  | 
| @@ -434,9 +444,9 @@ module StrongMigrations | |
| 434 444 | 
             
                  if function
         | 
| 435 445 | 
             
                    # update_all(column: Arel.sql(default)) also works in newer versions of Active Record
         | 
| 436 446 | 
             
                    update_expr = "#{quote_column_if_needed(column)} = #{default}"
         | 
| 437 | 
            -
                    "#{model}.unscoped.in_batches do |relation| \n      relation.where(#{column}: nil).update_all(#{update_expr.inspect})\n      sleep(0.01)\n    end"
         | 
| 447 | 
            +
                    "#{model}.unscoped.in_batches(of: 10000) do |relation| \n      relation.where(#{column}: nil).update_all(#{update_expr.inspect})\n      sleep(0.01)\n    end"
         | 
| 438 448 | 
             
                  else
         | 
| 439 | 
            -
                    "#{model}.unscoped.in_batches do |relation| \n      relation.update_all #{column}: #{default.inspect}\n      sleep(0.01)\n    end"
         | 
| 449 | 
            +
                    "#{model}.unscoped.in_batches(of: 10000) do |relation| \n      relation.where(#{column}: nil).update_all #{column}: #{default.inspect}\n      sleep(0.01)\n    end"
         | 
| 440 450 | 
             
                  end
         | 
| 441 451 | 
             
                end
         | 
| 442 452 |  | 
| @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            module StrongMigrations
         | 
| 2 2 | 
             
              module SafeMethods
         | 
| 3 3 | 
             
                def safe_by_default_method?(method)
         | 
| 4 | 
            -
                  StrongMigrations.safe_by_default && [:add_index, :add_belongs_to, :add_reference, :remove_index, :add_foreign_key, :add_check_constraint, :change_column_null].include?(method)
         | 
| 4 | 
            +
                  StrongMigrations.safe_by_default && !version_safe? && [:add_index, :add_belongs_to, :add_reference, :remove_index, :add_foreign_key, :add_check_constraint, :change_column_null].include?(method)
         | 
| 5 5 | 
             
                end
         | 
| 6 6 |  | 
| 7 7 | 
             
                def safe_add_index(*args, **options)
         | 
| @@ -76,11 +76,38 @@ module StrongMigrations | |
| 76 76 | 
             
                  end
         | 
| 77 77 | 
             
                end
         | 
| 78 78 |  | 
| 79 | 
            -
                def safe_change_column_null(add_args, validate_args, change_args, remove_args, default, constraints)
         | 
| 79 | 
            +
                def safe_change_column_null(add_args, validate_args, change_args, remove_args, table, column, default, constraints)
         | 
| 80 80 | 
             
                  @migration.reversible do |dir|
         | 
| 81 81 | 
             
                    dir.up do
         | 
| 82 82 | 
             
                      unless default.nil?
         | 
| 83 | 
            -
                         | 
| 83 | 
            +
                        # TODO search for parent model if needed
         | 
| 84 | 
            +
                        if connection.pool != ActiveRecord::Base.connection_pool
         | 
| 85 | 
            +
                          raise_error :change_column_null,
         | 
| 86 | 
            +
                            code: backfill_code(table, column, default)
         | 
| 87 | 
            +
                        end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                        model =
         | 
| 90 | 
            +
                          Class.new(ActiveRecord::Base) do
         | 
| 91 | 
            +
                            self.table_name = table
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                            def self.to_s
         | 
| 94 | 
            +
                              "Backfill"
         | 
| 95 | 
            +
                            end
         | 
| 96 | 
            +
                          end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                        update_sql =
         | 
| 99 | 
            +
                          model.connection_pool.with_connection do |c|
         | 
| 100 | 
            +
                            quoted_column = c.quote_column_name(column)
         | 
| 101 | 
            +
                            quoted_default = c.quote_default_expression(default, c.send(:column_for, table, column))
         | 
| 102 | 
            +
                            "#{quoted_column} = #{quoted_default}"
         | 
| 103 | 
            +
                          end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                        @migration.say("Backfilling default")
         | 
| 106 | 
            +
                        disable_transaction
         | 
| 107 | 
            +
                        model.unscoped.in_batches(of: 10000) do |relation|
         | 
| 108 | 
            +
                          relation.where(column => nil).update_all(update_sql)
         | 
| 109 | 
            +
                          sleep(0.01)
         | 
| 110 | 
            +
                        end
         | 
| 84 111 | 
             
                      end
         | 
| 85 112 |  | 
| 86 113 | 
             
                      add_options = add_args.extract_options!
         | 
    
        metadata
    CHANGED
    
    | @@ -1,16 +1,15 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: strong_migrations
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 2.1 | 
| 4 | 
            +
              version: 2.2.1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Andrew Kane
         | 
| 8 8 | 
             
            - Bob Remeika
         | 
| 9 9 | 
             
            - David Waller
         | 
| 10 | 
            -
            autorequire:
         | 
| 11 10 | 
             
            bindir: bin
         | 
| 12 11 | 
             
            cert_chain: []
         | 
| 13 | 
            -
            date:  | 
| 12 | 
            +
            date: 2025-03-22 00:00:00.000000000 Z
         | 
| 14 13 | 
             
            dependencies:
         | 
| 15 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 16 15 | 
             
              name: activerecord
         | 
| @@ -18,15 +17,14 @@ dependencies: | |
| 18 17 | 
             
                requirements:
         | 
| 19 18 | 
             
                - - ">="
         | 
| 20 19 | 
             
                  - !ruby/object:Gem::Version
         | 
| 21 | 
            -
                    version: ' | 
| 20 | 
            +
                    version: '7'
         | 
| 22 21 | 
             
              type: :runtime
         | 
| 23 22 | 
             
              prerelease: false
         | 
| 24 23 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 25 24 | 
             
                requirements:
         | 
| 26 25 | 
             
                - - ">="
         | 
| 27 26 | 
             
                  - !ruby/object:Gem::Version
         | 
| 28 | 
            -
                    version: ' | 
| 29 | 
            -
            description:
         | 
| 27 | 
            +
                    version: '7'
         | 
| 30 28 | 
             
            email:
         | 
| 31 29 | 
             
            - andrew@ankane.org
         | 
| 32 30 | 
             
            - bob.remeika@gmail.com
         | 
| @@ -60,7 +58,6 @@ homepage: https://github.com/ankane/strong_migrations | |
| 60 58 | 
             
            licenses:
         | 
| 61 59 | 
             
            - MIT
         | 
| 62 60 | 
             
            metadata: {}
         | 
| 63 | 
            -
            post_install_message:
         | 
| 64 61 | 
             
            rdoc_options: []
         | 
| 65 62 | 
             
            require_paths:
         | 
| 66 63 | 
             
            - lib
         | 
| @@ -75,8 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 75 72 | 
             
                - !ruby/object:Gem::Version
         | 
| 76 73 | 
             
                  version: '0'
         | 
| 77 74 | 
             
            requirements: []
         | 
| 78 | 
            -
            rubygems_version: 3. | 
| 79 | 
            -
            signing_key:
         | 
| 75 | 
            +
            rubygems_version: 3.6.2
         | 
| 80 76 | 
             
            specification_version: 4
         | 
| 81 77 | 
             
            summary: Catch unsafe migrations in development
         | 
| 82 78 | 
             
            test_files: []
         |