strong_migrations 0.7.4 → 0.7.5
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 +4 -0
- data/LICENSE.txt +1 -1
- data/README.md +101 -29
- data/lib/strong_migrations.rb +24 -0
- data/lib/strong_migrations/checker.rb +52 -5
- data/lib/strong_migrations/safe_methods.rb +14 -1
- 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: 2361d565fefd198370b908622526d01c4b5052f5361947c6410d1176b21b20a9
         | 
| 4 | 
            +
              data.tar.gz: c9ee9a7b444980b957f60dbb09290a5296a94e9f121af6b1d83756b2645ab54e
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: ca57e53122e37d7674143a48a5699bacf07f07f3c638abbf71331a3c4686a1e08f4f1fa02160e99ea0e21ed3a811aa43dc11581143b0229f5201ddc335fd792b
         | 
| 7 | 
            +
              data.tar.gz: 4b760e24cda9a14efed64300992645ec9e4b207bbafb5210bdfd2195764980cc0ab08ebbd6693aac6518d5302da918a74e20d5d282d654700efe89e4a8418a63
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    
    
        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[6. | 
| 46 | 
            +
            class RemoveColumn < ActiveRecord::Migration[6.1]
         | 
| 47 47 | 
             
              def change
         | 
| 48 48 | 
             
                safety_assured { remove_column :users, :name }
         | 
| 49 49 | 
             
              end
         | 
| @@ -66,6 +66,7 @@ Potentially dangerous operations: | |
| 66 66 | 
             
            - [renaming a column](#renaming-a-column)
         | 
| 67 67 | 
             
            - [renaming a table](#renaming-a-table)
         | 
| 68 68 | 
             
            - [creating a table with the force option](#creating-a-table-with-the-force-option)
         | 
| 69 | 
            +
            - [adding a check constraint](#adding-a-check-constraint)
         | 
| 69 70 | 
             
            - [setting NOT NULL on an existing column](#setting-not-null-on-an-existing-column)
         | 
| 70 71 | 
             
            - [executing SQL directly](#executing-SQL-directly)
         | 
| 71 72 |  | 
| @@ -89,7 +90,7 @@ You can also add [custom checks](#custom-checks) or [disable specific checks](#d | |
| 89 90 | 
             
            Active Record caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
         | 
| 90 91 |  | 
| 91 92 | 
             
            ```ruby
         | 
| 92 | 
            -
            class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6. | 
| 93 | 
            +
            class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.1]
         | 
| 93 94 | 
             
              def change
         | 
| 94 95 | 
             
                remove_column :users, :some_column
         | 
| 95 96 | 
             
              end
         | 
| @@ -110,7 +111,7 @@ end | |
| 110 111 | 
             
            3. Write a migration to remove the column (wrap in `safety_assured` block)
         | 
| 111 112 |  | 
| 112 113 | 
             
              ```ruby
         | 
| 113 | 
            -
              class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6. | 
| 114 | 
            +
              class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.1]
         | 
| 114 115 | 
             
                def change
         | 
| 115 116 | 
             
                  safety_assured { remove_column :users, :some_column }
         | 
| 116 117 | 
             
                end
         | 
| @@ -126,7 +127,7 @@ end | |
| 126 127 | 
             
            In earlier versions of Postgres, MySQL, and MariaDB, adding a column with a default value to an existing table causes the entire table to be rewritten. During this time, reads and writes are blocked in Postgres, and writes are blocked in MySQL and MariaDB.
         | 
| 127 128 |  | 
| 128 129 | 
             
            ```ruby
         | 
| 129 | 
            -
            class AddSomeColumnToUsers < ActiveRecord::Migration[6. | 
| 130 | 
            +
            class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
         | 
| 130 131 | 
             
              def change
         | 
| 131 132 | 
             
                add_column :users, :some_column, :text, default: "default_value"
         | 
| 132 133 | 
             
              end
         | 
| @@ -140,7 +141,7 @@ In Postgres 11+, MySQL 8.0.12+, and MariaDB 10.3.2+, this no longer requires a t | |
| 140 141 | 
             
            Instead, add the column without a default value, then change the default.
         | 
| 141 142 |  | 
| 142 143 | 
             
            ```ruby
         | 
| 143 | 
            -
            class AddSomeColumnToUsers < ActiveRecord::Migration[6. | 
| 144 | 
            +
            class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
         | 
| 144 145 | 
             
              def up
         | 
| 145 146 | 
             
                add_column :users, :some_column, :text
         | 
| 146 147 | 
             
                change_column_default :users, :some_column, "default_value"
         | 
| @@ -161,7 +162,7 @@ See the next section for how to backfill. | |
| 161 162 | 
             
            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/).
         | 
| 162 163 |  | 
| 163 164 | 
             
            ```ruby
         | 
| 164 | 
            -
            class AddSomeColumnToUsers < ActiveRecord::Migration[6. | 
| 165 | 
            +
            class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
         | 
| 165 166 | 
             
              def change
         | 
| 166 167 | 
             
                add_column :users, :some_column, :text
         | 
| 167 168 | 
             
                User.update_all some_column: "default_value"
         | 
| @@ -176,7 +177,7 @@ Also, running a single query to update data can cause issues for large tables. | |
| 176 177 | 
             
            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!`.
         | 
| 177 178 |  | 
| 178 179 | 
             
            ```ruby
         | 
| 179 | 
            -
            class BackfillSomeColumn < ActiveRecord::Migration[6. | 
| 180 | 
            +
            class BackfillSomeColumn < ActiveRecord::Migration[6.1]
         | 
| 180 181 | 
             
              disable_ddl_transaction!
         | 
| 181 182 |  | 
| 182 183 | 
             
              def up
         | 
| @@ -195,7 +196,7 @@ end | |
| 195 196 | 
             
            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.
         | 
| 196 197 |  | 
| 197 198 | 
             
            ```ruby
         | 
| 198 | 
            -
            class ChangeSomeColumnType < ActiveRecord::Migration[6. | 
| 199 | 
            +
            class ChangeSomeColumnType < ActiveRecord::Migration[6.1]
         | 
| 199 200 | 
             
              def change
         | 
| 200 201 | 
             
                change_column :users, :some_column, :new_type
         | 
| 201 202 | 
             
              end
         | 
| @@ -234,7 +235,7 @@ A safer approach is to: | |
| 234 235 | 
             
            Renaming a column that’s in use will cause errors in your application.
         | 
| 235 236 |  | 
| 236 237 | 
             
            ```ruby
         | 
| 237 | 
            -
            class RenameSomeColumn < ActiveRecord::Migration[6. | 
| 238 | 
            +
            class RenameSomeColumn < ActiveRecord::Migration[6.1]
         | 
| 238 239 | 
             
              def change
         | 
| 239 240 | 
             
                rename_column :users, :some_column, :new_name
         | 
| 240 241 | 
             
              end
         | 
| @@ -259,7 +260,7 @@ A safer approach is to: | |
| 259 260 | 
             
            Renaming a table that’s in use will cause errors in your application.
         | 
| 260 261 |  | 
| 261 262 | 
             
            ```ruby
         | 
| 262 | 
            -
            class RenameUsersToCustomers < ActiveRecord::Migration[6. | 
| 263 | 
            +
            class RenameUsersToCustomers < ActiveRecord::Migration[6.1]
         | 
| 263 264 | 
             
              def change
         | 
| 264 265 | 
             
                rename_table :users, :customers
         | 
| 265 266 | 
             
              end
         | 
| @@ -284,7 +285,7 @@ A safer approach is to: | |
| 284 285 | 
             
            The `force` option can drop an existing table.
         | 
| 285 286 |  | 
| 286 287 | 
             
            ```ruby
         | 
| 287 | 
            -
            class CreateUsers < ActiveRecord::Migration[6. | 
| 288 | 
            +
            class CreateUsers < ActiveRecord::Migration[6.1]
         | 
| 288 289 | 
             
              def change
         | 
| 289 290 | 
             
                create_table :users, force: true do |t|
         | 
| 290 291 | 
             
                  # ...
         | 
| @@ -298,7 +299,7 @@ end | |
| 298 299 | 
             
            Create tables without the `force` option.
         | 
| 299 300 |  | 
| 300 301 | 
             
            ```ruby
         | 
| 301 | 
            -
            class CreateUsers < ActiveRecord::Migration[6. | 
| 302 | 
            +
            class CreateUsers < ActiveRecord::Migration[6.1]
         | 
| 302 303 | 
             
              def change
         | 
| 303 304 | 
             
                create_table :users do |t|
         | 
| 304 305 | 
             
                  # ...
         | 
| @@ -309,6 +310,48 @@ end | |
| 309 310 |  | 
| 310 311 | 
             
            If you intend to drop an existing table, run `drop_table` first.
         | 
| 311 312 |  | 
| 313 | 
            +
            ### Adding a check constraint
         | 
| 314 | 
            +
             | 
| 315 | 
            +
            :turtle: Safe by default available
         | 
| 316 | 
            +
             | 
| 317 | 
            +
            #### Bad
         | 
| 318 | 
            +
             | 
| 319 | 
            +
            Adding a check constraint blocks reads and writes in Postgres and blocks writes in MySQL and MariaDB while every row is checked.
         | 
| 320 | 
            +
             | 
| 321 | 
            +
            ```ruby
         | 
| 322 | 
            +
            class AddCheckConstraint < ActiveRecord::Migration[6.1]
         | 
| 323 | 
            +
              def change
         | 
| 324 | 
            +
                add_check_constraint :users, "price > 0", name: "price_check"
         | 
| 325 | 
            +
              end
         | 
| 326 | 
            +
            end
         | 
| 327 | 
            +
            ```
         | 
| 328 | 
            +
             | 
| 329 | 
            +
            #### Good - Postgres
         | 
| 330 | 
            +
             | 
| 331 | 
            +
            Add the check constraint without validating existing rows:
         | 
| 332 | 
            +
             | 
| 333 | 
            +
            ```ruby
         | 
| 334 | 
            +
            class AddCheckConstraint < ActiveRecord::Migration[6.1]
         | 
| 335 | 
            +
              def change
         | 
| 336 | 
            +
                add_check_constraint :users, "price > 0", name: "price_check", validate: false
         | 
| 337 | 
            +
              end
         | 
| 338 | 
            +
            end
         | 
| 339 | 
            +
            ```
         | 
| 340 | 
            +
             | 
| 341 | 
            +
            Then validate them in a separate migration.
         | 
| 342 | 
            +
             | 
| 343 | 
            +
            ```ruby
         | 
| 344 | 
            +
            class ValidateCheckConstraint < ActiveRecord::Migration[6.1]
         | 
| 345 | 
            +
              def change
         | 
| 346 | 
            +
                validate_check_constraint :users, name: "price_check"
         | 
| 347 | 
            +
              end
         | 
| 348 | 
            +
            end
         | 
| 349 | 
            +
            ```
         | 
| 350 | 
            +
             | 
| 351 | 
            +
            #### Good - MySQL and MariaDB
         | 
| 352 | 
            +
             | 
| 353 | 
            +
            [Let us know](https://github.com/ankane/strong_migrations/issues/new) if you have a safe way to do this (check constraints can be added with `NOT ENFORCED`, but enforcing blocks writes).
         | 
| 354 | 
            +
             | 
| 312 355 | 
             
            ### Setting NOT NULL on an existing column
         | 
| 313 356 |  | 
| 314 357 | 
             
            :turtle: Safe by default available
         | 
| @@ -318,7 +361,7 @@ If you intend to drop an existing table, run `drop_table` first. | |
| 318 361 | 
             
            Setting `NOT NULL` on an existing column blocks reads and writes while every row is checked.
         | 
| 319 362 |  | 
| 320 363 | 
             
            ```ruby
         | 
| 321 | 
            -
            class SetSomeColumnNotNull < ActiveRecord::Migration[6. | 
| 364 | 
            +
            class SetSomeColumnNotNull < ActiveRecord::Migration[6.1]
         | 
| 322 365 | 
             
              def change
         | 
| 323 366 | 
             
                change_column_null :users, :some_column, false
         | 
| 324 367 | 
             
              end
         | 
| @@ -327,7 +370,19 @@ end | |
| 327 370 |  | 
| 328 371 | 
             
            #### Good - Postgres
         | 
| 329 372 |  | 
| 330 | 
            -
            Instead, add a check constraint | 
| 373 | 
            +
            Instead, add a check constraint.
         | 
| 374 | 
            +
             | 
| 375 | 
            +
            For Rails 6.1, use:
         | 
| 376 | 
            +
             | 
| 377 | 
            +
            ```ruby
         | 
| 378 | 
            +
            class SetSomeColumnNotNull < ActiveRecord::Migration[6.1]
         | 
| 379 | 
            +
              def change
         | 
| 380 | 
            +
                add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
         | 
| 381 | 
            +
              end
         | 
| 382 | 
            +
            end
         | 
| 383 | 
            +
            ```
         | 
| 384 | 
            +
             | 
| 385 | 
            +
            For Rails < 6.1, use:
         | 
| 331 386 |  | 
| 332 387 | 
             
            ```ruby
         | 
| 333 388 | 
             
            class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
         | 
| @@ -339,7 +394,23 @@ class SetSomeColumnNotNull < ActiveRecord::Migration[6.0] | |
| 339 394 | 
             
            end
         | 
| 340 395 | 
             
            ```
         | 
| 341 396 |  | 
| 342 | 
            -
            Then validate it in a separate migration. A `NOT NULL` check constraint is [functionally equivalent](https://medium.com/doctolib/adding-a-not-null-constraint-on-pg-faster-with-minimal-locking-38b2c00c4d1c) to setting `NOT NULL` on the column | 
| 397 | 
            +
            Then validate it in a separate migration. A `NOT NULL` check constraint is [functionally equivalent](https://medium.com/doctolib/adding-a-not-null-constraint-on-pg-faster-with-minimal-locking-38b2c00c4d1c) to setting `NOT NULL` on the column (but it won’t show up in `schema.rb` in Rails < 6.1). In Postgres 12+, once the check constraint is validated, you can safely set `NOT NULL` on the column and drop the check constraint.
         | 
| 398 | 
            +
             | 
| 399 | 
            +
            For Rails 6.1, use:
         | 
| 400 | 
            +
             | 
| 401 | 
            +
            ```ruby
         | 
| 402 | 
            +
            class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.1]
         | 
| 403 | 
            +
              def change
         | 
| 404 | 
            +
                validate_check_constraint :users, name: "users_some_column_null"
         | 
| 405 | 
            +
             | 
| 406 | 
            +
                # in Postgres 12+, you can then safely set NOT NULL on the column
         | 
| 407 | 
            +
                change_column_null :users, :some_column, false
         | 
| 408 | 
            +
                remove_check_constraint :users, name: "users_some_column_null"
         | 
| 409 | 
            +
              end
         | 
| 410 | 
            +
            end
         | 
| 411 | 
            +
            ```
         | 
| 412 | 
            +
             | 
| 413 | 
            +
            For Rails < 6.1, use:
         | 
| 343 414 |  | 
| 344 415 | 
             
            ```ruby
         | 
| 345 416 | 
             
            class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
         | 
| @@ -366,7 +437,7 @@ end | |
| 366 437 | 
             
            Strong Migrations can’t ensure safety for raw SQL statements. Make really sure that what you’re doing is safe, then use:
         | 
| 367 438 |  | 
| 368 439 | 
             
            ```ruby
         | 
| 369 | 
            -
            class ExecuteSQL < ActiveRecord::Migration[6. | 
| 440 | 
            +
            class ExecuteSQL < ActiveRecord::Migration[6.1]
         | 
| 370 441 | 
             
              def change
         | 
| 371 442 | 
             
                safety_assured { execute "..." }
         | 
| 372 443 | 
             
              end
         | 
| @@ -382,7 +453,7 @@ end | |
| 382 453 | 
             
            In Postgres, adding an index non-concurrently blocks writes.
         | 
| 383 454 |  | 
| 384 455 | 
             
            ```ruby
         | 
| 385 | 
            -
            class AddSomeIndexToUsers < ActiveRecord::Migration[6. | 
| 456 | 
            +
            class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
         | 
| 386 457 | 
             
              def change
         | 
| 387 458 | 
             
                add_index :users, :some_column
         | 
| 388 459 | 
             
              end
         | 
| @@ -394,7 +465,7 @@ end | |
| 394 465 | 
             
            Add indexes concurrently.
         | 
| 395 466 |  | 
| 396 467 | 
             
            ```ruby
         | 
| 397 | 
            -
            class AddSomeIndexToUsers < ActiveRecord::Migration[6. | 
| 468 | 
            +
            class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
         | 
| 398 469 | 
             
              disable_ddl_transaction!
         | 
| 399 470 |  | 
| 400 471 | 
             
              def change
         | 
| @@ -420,7 +491,7 @@ rails g index table column | |
| 420 491 | 
             
            Rails adds an index non-concurrently to references by default, which blocks writes in Postgres.
         | 
| 421 492 |  | 
| 422 493 | 
             
            ```ruby
         | 
| 423 | 
            -
            class AddReferenceToUsers < ActiveRecord::Migration[6. | 
| 494 | 
            +
            class AddReferenceToUsers < ActiveRecord::Migration[6.1]
         | 
| 424 495 | 
             
              def change
         | 
| 425 496 | 
             
                add_reference :users, :city
         | 
| 426 497 | 
             
              end
         | 
| @@ -432,7 +503,7 @@ end | |
| 432 503 | 
             
            Make sure the index is added concurrently.
         | 
| 433 504 |  | 
| 434 505 | 
             
            ```ruby
         | 
| 435 | 
            -
            class AddReferenceToUsers < ActiveRecord::Migration[6. | 
| 506 | 
            +
            class AddReferenceToUsers < ActiveRecord::Migration[6.1]
         | 
| 436 507 | 
             
              disable_ddl_transaction!
         | 
| 437 508 |  | 
| 438 509 | 
             
              def change
         | 
| @@ -450,7 +521,7 @@ end | |
| 450 521 | 
             
            In Postgres, adding a foreign key blocks writes on both tables.
         | 
| 451 522 |  | 
| 452 523 | 
             
            ```ruby
         | 
| 453 | 
            -
            class AddForeignKeyOnUsers < ActiveRecord::Migration[6. | 
| 524 | 
            +
            class AddForeignKeyOnUsers < ActiveRecord::Migration[6.1]
         | 
| 454 525 | 
             
              def change
         | 
| 455 526 | 
             
                add_foreign_key :users, :orders
         | 
| 456 527 | 
             
              end
         | 
| @@ -460,7 +531,7 @@ end | |
| 460 531 | 
             
            or
         | 
| 461 532 |  | 
| 462 533 | 
             
            ```ruby
         | 
| 463 | 
            -
            class AddReferenceToUsers < ActiveRecord::Migration[6. | 
| 534 | 
            +
            class AddReferenceToUsers < ActiveRecord::Migration[6.1]
         | 
| 464 535 | 
             
              def change
         | 
| 465 536 | 
             
                add_reference :users, :order, foreign_key: true
         | 
| 466 537 | 
             
              end
         | 
| @@ -474,7 +545,7 @@ Add the foreign key without validating existing rows, then validate them in a se | |
| 474 545 | 
             
            For Rails 5.2+, use:
         | 
| 475 546 |  | 
| 476 547 | 
             
            ```ruby
         | 
| 477 | 
            -
            class AddForeignKeyOnUsers < ActiveRecord::Migration[6. | 
| 548 | 
            +
            class AddForeignKeyOnUsers < ActiveRecord::Migration[6.1]
         | 
| 478 549 | 
             
              def change
         | 
| 479 550 | 
             
                add_foreign_key :users, :orders, validate: false
         | 
| 480 551 | 
             
              end
         | 
| @@ -484,7 +555,7 @@ end | |
| 484 555 | 
             
            Then:
         | 
| 485 556 |  | 
| 486 557 | 
             
            ```ruby
         | 
| 487 | 
            -
            class ValidateForeignKeyOnUsers < ActiveRecord::Migration[6. | 
| 558 | 
            +
            class ValidateForeignKeyOnUsers < ActiveRecord::Migration[6.1]
         | 
| 488 559 | 
             
              def change
         | 
| 489 560 | 
             
                validate_foreign_key :users, :orders
         | 
| 490 561 | 
             
              end
         | 
| @@ -522,7 +593,7 @@ end | |
| 522 593 | 
             
            In Postgres, there’s no equality operator for the `json` column type, which can cause errors for existing `SELECT DISTINCT` queries in your application.
         | 
| 523 594 |  | 
| 524 595 | 
             
            ```ruby
         | 
| 525 | 
            -
            class AddPropertiesToUsers < ActiveRecord::Migration[6. | 
| 596 | 
            +
            class AddPropertiesToUsers < ActiveRecord::Migration[6.1]
         | 
| 526 597 | 
             
              def change
         | 
| 527 598 | 
             
                add_column :users, :properties, :json
         | 
| 528 599 | 
             
              end
         | 
| @@ -534,7 +605,7 @@ end | |
| 534 605 | 
             
            Use `jsonb` instead.
         | 
| 535 606 |  | 
| 536 607 | 
             
            ```ruby
         | 
| 537 | 
            -
            class AddPropertiesToUsers < ActiveRecord::Migration[6. | 
| 608 | 
            +
            class AddPropertiesToUsers < ActiveRecord::Migration[6.1]
         | 
| 538 609 | 
             
              def change
         | 
| 539 610 | 
             
                add_column :users, :properties, :jsonb
         | 
| 540 611 | 
             
              end
         | 
| @@ -548,7 +619,7 @@ end | |
| 548 619 | 
             
            Adding a non-unique index with more than three columns rarely improves performance.
         | 
| 549 620 |  | 
| 550 621 | 
             
            ```ruby
         | 
| 551 | 
            -
            class AddSomeIndexToUsers < ActiveRecord::Migration[6. | 
| 622 | 
            +
            class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
         | 
| 552 623 | 
             
              def change
         | 
| 553 624 | 
             
                add_index :users, [:a, :b, :c, :d]
         | 
| 554 625 | 
             
              end
         | 
| @@ -560,7 +631,7 @@ end | |
| 560 631 | 
             
            Instead, start an index with columns that narrow down the results the most.
         | 
| 561 632 |  | 
| 562 633 | 
             
            ```ruby
         | 
| 563 | 
            -
            class AddSomeIndexToUsers < ActiveRecord::Migration[6. | 
| 634 | 
            +
            class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
         | 
| 564 635 | 
             
              def change
         | 
| 565 636 | 
             
                add_index :users, [:b, :d]
         | 
| 566 637 | 
             
              end
         | 
| @@ -574,7 +645,7 @@ For Postgres, be sure to add them concurrently. | |
| 574 645 | 
             
            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.
         | 
| 575 646 |  | 
| 576 647 | 
             
            ```ruby
         | 
| 577 | 
            -
            class MySafeMigration < ActiveRecord::Migration[6. | 
| 648 | 
            +
            class MySafeMigration < ActiveRecord::Migration[6.1]
         | 
| 578 649 | 
             
              def change
         | 
| 579 650 | 
             
                safety_assured { remove_column :users, :some_column }
         | 
| 580 651 | 
             
              end
         | 
| @@ -591,6 +662,7 @@ Make operations safe by default. | |
| 591 662 |  | 
| 592 663 | 
             
            - adding and removing an index
         | 
| 593 664 | 
             
            - adding a foreign key
         | 
| 665 | 
            +
            - adding a check constraint
         | 
| 594 666 | 
             
            - setting NOT NULL on an existing column
         | 
| 595 667 |  | 
| 596 668 | 
             
            Add to `config/initializers/strong_migrations.rb`:
         | 
    
        data/lib/strong_migrations.rb
    CHANGED
    
    | @@ -216,6 +216,30 @@ end", | |
| 216 216 |  | 
| 217 217 | 
             
                validate_foreign_key:
         | 
| 218 218 | 
             
            "Validating a foreign key while writes are blocked is dangerous.
         | 
| 219 | 
            +
            Use disable_ddl_transaction! or a separate migration.",
         | 
| 220 | 
            +
             | 
| 221 | 
            +
                add_check_constraint:
         | 
| 222 | 
            +
            "Adding a check constraint key blocks reads and writes while every row is checked.
         | 
| 223 | 
            +
            Instead, add the check constraint without validating existing rows,
         | 
| 224 | 
            +
            then validate them in a separate migration.
         | 
| 225 | 
            +
             | 
| 226 | 
            +
            class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
         | 
| 227 | 
            +
              def change
         | 
| 228 | 
            +
                %{add_check_constraint_code}
         | 
| 229 | 
            +
              end
         | 
| 230 | 
            +
            end
         | 
| 231 | 
            +
             | 
| 232 | 
            +
            class Validate%{migration_name} < ActiveRecord::Migration%{migration_suffix}
         | 
| 233 | 
            +
              def change
         | 
| 234 | 
            +
                %{validate_check_constraint_code}
         | 
| 235 | 
            +
              end
         | 
| 236 | 
            +
            end",
         | 
| 237 | 
            +
             | 
| 238 | 
            +
                add_check_constraint_mysql:
         | 
| 239 | 
            +
            "Adding a check constraint to an existing table is not safe with your database engine.",
         | 
| 240 | 
            +
             | 
| 241 | 
            +
                validate_check_constraint:
         | 
| 242 | 
            +
            "Validating a check constraint while writes are blocked is dangerous.
         | 
| 219 243 | 
             
            Use disable_ddl_transaction! or a separate migration."
         | 
| 220 244 | 
             
              }
         | 
| 221 245 | 
             
              self.enabled_checks = (error_messages.keys - [:remove_index]).map { |k| [k, {}] }.to_h
         | 
| @@ -231,18 +231,38 @@ Then add the foreign key in separate migrations." | |
| 231 231 | 
             
                            validate_code = constraint_str("ALTER TABLE %s VALIDATE CONSTRAINT %s", [table, constraint_name])
         | 
| 232 232 | 
             
                            remove_code = constraint_str("ALTER TABLE %s DROP CONSTRAINT %s", [table, constraint_name])
         | 
| 233 233 |  | 
| 234 | 
            -
                            validate_constraint_code = | 
| 234 | 
            +
                            validate_constraint_code =
         | 
| 235 | 
            +
                              if ar_version >= 6.1
         | 
| 236 | 
            +
                                String.new(command_str(:validate_check_constraint, [table, {name: constraint_name}]))
         | 
| 237 | 
            +
                              else
         | 
| 238 | 
            +
                                String.new(safety_assured_str(validate_code))
         | 
| 239 | 
            +
                              end
         | 
| 240 | 
            +
             | 
| 235 241 | 
             
                            if postgresql_version >= Gem::Version.new("12")
         | 
| 236 242 | 
             
                              change_args = [table, column, null]
         | 
| 237 243 |  | 
| 238 244 | 
             
                              validate_constraint_code << "\n    #{command_str(:change_column_null, change_args)}"
         | 
| 239 | 
            -
             | 
| 245 | 
            +
             | 
| 246 | 
            +
                              if ar_version >= 6.1
         | 
| 247 | 
            +
                                validate_constraint_code << "\n    #{command_str(:remove_check_constraint, [table, {name: constraint_name}])}"
         | 
| 248 | 
            +
                              else
         | 
| 249 | 
            +
                                validate_constraint_code << "\n    #{safety_assured_str(remove_code)}"
         | 
| 250 | 
            +
                              end
         | 
| 240 251 | 
             
                            end
         | 
| 241 252 |  | 
| 242 253 | 
             
                            return safe_change_column_null(add_code, validate_code, change_args, remove_code) if StrongMigrations.safe_by_default
         | 
| 243 254 |  | 
| 255 | 
            +
                            add_constraint_code =
         | 
| 256 | 
            +
                              if ar_version >= 6.1
         | 
| 257 | 
            +
                                # only quote when needed
         | 
| 258 | 
            +
                                expr_column = column.to_s =~ /\A[a-z0-9_]+\z/ ? column : connection.quote_column_name(column)
         | 
| 259 | 
            +
                                command_str(:add_check_constraint, [table, "#{expr_column} IS NOT NULL", {name: constraint_name, validate: false}])
         | 
| 260 | 
            +
                              else
         | 
| 261 | 
            +
                                safety_assured_str(add_code)
         | 
| 262 | 
            +
                              end
         | 
| 263 | 
            +
             | 
| 244 264 | 
             
                            raise_error :change_column_null_postgresql,
         | 
| 245 | 
            -
                              add_constraint_code:  | 
| 265 | 
            +
                              add_constraint_code: add_constraint_code,
         | 
| 246 266 | 
             
                              validate_constraint_code: validate_constraint_code
         | 
| 247 267 | 
             
                          end
         | 
| 248 268 | 
             
                        elsif mysql? || mariadb?
         | 
| @@ -257,10 +277,10 @@ Then add the foreign key in separate migrations." | |
| 257 277 | 
             
                      options ||= {}
         | 
| 258 278 |  | 
| 259 279 | 
             
                      # always validated before 5.2
         | 
| 260 | 
            -
                      validate = options.fetch(:validate, true) ||  | 
| 280 | 
            +
                      validate = options.fetch(:validate, true) || ar_version < 5.2
         | 
| 261 281 |  | 
| 262 282 | 
             
                      if postgresql? && validate
         | 
| 263 | 
            -
                        if  | 
| 283 | 
            +
                        if ar_version < 5.2
         | 
| 264 284 | 
             
                          # fk name logic from rails
         | 
| 265 285 | 
             
                          primary_key = options[:primary_key] || "id"
         | 
| 266 286 | 
             
                          column = options[:column] || "#{to_table.to_s.singularize}_id"
         | 
| @@ -287,6 +307,29 @@ Then add the foreign key in separate migrations." | |
| 287 307 | 
             
                      if postgresql? && writes_blocked?
         | 
| 288 308 | 
             
                        raise_error :validate_foreign_key
         | 
| 289 309 | 
             
                      end
         | 
| 310 | 
            +
                    when :add_check_constraint
         | 
| 311 | 
            +
                      table, expression, options = args
         | 
| 312 | 
            +
                      options ||= {}
         | 
| 313 | 
            +
             | 
| 314 | 
            +
                      if !new_table?(table)
         | 
| 315 | 
            +
                        if postgresql? && options[:validate] != false
         | 
| 316 | 
            +
                          add_options = options.merge(validate: false)
         | 
| 317 | 
            +
                          name = options[:name] || @migration.check_constraint_options(table, expression, options)[:name]
         | 
| 318 | 
            +
                          validate_options = {name: name}
         | 
| 319 | 
            +
             | 
| 320 | 
            +
                          return safe_add_check_constraint(table, expression, add_options, validate_options) if StrongMigrations.safe_by_default
         | 
| 321 | 
            +
             | 
| 322 | 
            +
                          raise_error :add_check_constraint,
         | 
| 323 | 
            +
                            add_check_constraint_code: command_str("add_check_constraint", [table, expression, add_options]),
         | 
| 324 | 
            +
                            validate_check_constraint_code: command_str("validate_check_constraint", [table, validate_options])
         | 
| 325 | 
            +
                        elsif mysql? || mariadb?
         | 
| 326 | 
            +
                          raise_error :add_check_constraint_mysql
         | 
| 327 | 
            +
                        end
         | 
| 328 | 
            +
                      end
         | 
| 329 | 
            +
                    when :validate_check_constraint
         | 
| 330 | 
            +
                      if postgresql? && writes_blocked?
         | 
| 331 | 
            +
                        raise_error :validate_check_constraint
         | 
| 332 | 
            +
                      end
         | 
| 290 333 | 
             
                    end
         | 
| 291 334 |  | 
| 292 335 | 
             
                    StrongMigrations.checks.each do |check|
         | 
| @@ -410,6 +453,10 @@ Then add the foreign key in separate migrations." | |
| 410 453 | 
             
                  Gem::Version.new(version)
         | 
| 411 454 | 
             
                end
         | 
| 412 455 |  | 
| 456 | 
            +
                def ar_version
         | 
| 457 | 
            +
                  ActiveRecord::VERSION::STRING.to_f
         | 
| 458 | 
            +
                end
         | 
| 459 | 
            +
             | 
| 413 460 | 
             
                def check_lock_timeout
         | 
| 414 461 | 
             
                  limit = StrongMigrations.lock_timeout_limit
         | 
| 415 462 |  | 
| @@ -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, :change_column_null].include?(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)
         | 
| 5 5 | 
             
                end
         | 
| 6 6 |  | 
| 7 7 | 
             
                # TODO check if invalid index with expected name exists and remove if needed
         | 
| @@ -67,6 +67,19 @@ module StrongMigrations | |
| 67 67 | 
             
                  end
         | 
| 68 68 | 
             
                end
         | 
| 69 69 |  | 
| 70 | 
            +
                def safe_add_check_constraint(table, expression, add_options, validate_options)
         | 
| 71 | 
            +
                  @migration.reversible do |dir|
         | 
| 72 | 
            +
                    dir.up do
         | 
| 73 | 
            +
                      @migration.add_check_constraint(table, expression, **add_options)
         | 
| 74 | 
            +
                      disable_transaction
         | 
| 75 | 
            +
                      @migration.validate_check_constraint(table, **validate_options)
         | 
| 76 | 
            +
                    end
         | 
| 77 | 
            +
                    dir.down do
         | 
| 78 | 
            +
                      @migration.remove_check_constraint(table, expression, **add_options)
         | 
| 79 | 
            +
                    end
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
             | 
| 70 83 | 
             
                def safe_change_column_null(add_code, validate_code, change_args, remove_code)
         | 
| 71 84 | 
             
                  @migration.reversible do |dir|
         | 
| 72 85 | 
             
                    dir.up do
         | 
    
        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: 0.7. | 
| 4 | 
            +
              version: 0.7.5
         | 
| 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:  | 
| 13 | 
            +
            date: 2021-01-13 00:00:00.000000000 Z
         | 
| 14 14 | 
             
            dependencies:
         | 
| 15 15 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 16 16 | 
             
              name: activerecord
         | 
| @@ -138,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 138 138 | 
             
                - !ruby/object:Gem::Version
         | 
| 139 139 | 
             
                  version: '0'
         | 
| 140 140 | 
             
            requirements: []
         | 
| 141 | 
            -
            rubygems_version: 3.2. | 
| 141 | 
            +
            rubygems_version: 3.2.3
         | 
| 142 142 | 
             
            signing_key:
         | 
| 143 143 | 
             
            specification_version: 4
         | 
| 144 144 | 
             
            summary: Catch unsafe migrations in development
         |