README.md in strong_migrations-0.7.8 vs README.md in strong_migrations-0.7.9

- old
+ new

@@ -41,11 +41,11 @@ self.ignored_columns = ["name"] end Deploy the code, then wrap this step in a safety_assured { ... } block. -class RemoveColumn < ActiveRecord::Migration[6.1] +class RemoveColumn < ActiveRecord::Migration[7.0] def change safety_assured { remove_column :users, :name } end end ``` @@ -88,11 +88,11 @@ #### Bad Active Record caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots. ```ruby -class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.1] +class RemoveSomeColumnFromUsers < ActiveRecord::Migration[7.0] def change remove_column :users, :some_column end end ``` @@ -109,11 +109,11 @@ 2. Deploy code 3. Write a migration to remove the column (wrap in `safety_assured` block) ```ruby - class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.1] + class RemoveSomeColumnFromUsers < ActiveRecord::Migration[7.0] def change safety_assured { remove_column :users, :some_column } end end ``` @@ -125,11 +125,11 @@ #### Bad 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. ```ruby -class AddSomeColumnToUsers < ActiveRecord::Migration[6.1] +class AddSomeColumnToUsers < ActiveRecord::Migration[7.0] def change add_column :users, :some_column, :text, default: "default_value" end end ``` @@ -139,11 +139,11 @@ #### Good Instead, add the column without a default value, then change the default. ```ruby -class AddSomeColumnToUsers < ActiveRecord::Migration[6.1] +class AddSomeColumnToUsers < ActiveRecord::Migration[7.0] def up add_column :users, :some_column, :text change_column_default :users, :some_column, "default_value" end @@ -160,11 +160,11 @@ #### Bad 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/). ```ruby -class AddSomeColumnToUsers < ActiveRecord::Migration[6.1] +class AddSomeColumnToUsers < ActiveRecord::Migration[7.0] def change add_column :users, :some_column, :text User.update_all some_column: "default_value" end end @@ -175,11 +175,11 @@ #### Good 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!`. ```ruby -class BackfillSomeColumn < ActiveRecord::Migration[6.1] +class BackfillSomeColumn < ActiveRecord::Migration[7.0] disable_ddl_transaction! def up User.unscoped.in_batches do |relation| relation.update_all some_column: "default_value" @@ -194,11 +194,11 @@ #### Bad 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. ```ruby -class ChangeSomeColumnType < ActiveRecord::Migration[6.1] +class ChangeSomeColumnType < ActiveRecord::Migration[7.0] def change change_column :users, :some_column, :new_type end end ``` @@ -233,11 +233,11 @@ #### Bad Renaming a column that’s in use will cause errors in your application. ```ruby -class RenameSomeColumn < ActiveRecord::Migration[6.1] +class RenameSomeColumn < ActiveRecord::Migration[7.0] def change rename_column :users, :some_column, :new_name end end ``` @@ -258,11 +258,11 @@ #### Bad Renaming a table that’s in use will cause errors in your application. ```ruby -class RenameUsersToCustomers < ActiveRecord::Migration[6.1] +class RenameUsersToCustomers < ActiveRecord::Migration[7.0] def change rename_table :users, :customers end end ``` @@ -283,11 +283,11 @@ #### Bad The `force` option can drop an existing table. ```ruby -class CreateUsers < ActiveRecord::Migration[6.1] +class CreateUsers < ActiveRecord::Migration[7.0] def change create_table :users, force: true do |t| # ... end end @@ -297,11 +297,11 @@ #### Good Create tables without the `force` option. ```ruby -class CreateUsers < ActiveRecord::Migration[6.1] +class CreateUsers < ActiveRecord::Migration[7.0] def change create_table :users do |t| # ... end end @@ -317,11 +317,11 @@ #### Bad Adding a check constraint blocks reads and writes in Postgres and blocks writes in MySQL and MariaDB while every row is checked. ```ruby -class AddCheckConstraint < ActiveRecord::Migration[6.1] +class AddCheckConstraint < ActiveRecord::Migration[7.0] def change add_check_constraint :users, "price > 0", name: "price_check" end end ``` @@ -329,21 +329,21 @@ #### Good - Postgres Add the check constraint without validating existing rows: ```ruby -class AddCheckConstraint < ActiveRecord::Migration[6.1] +class AddCheckConstraint < ActiveRecord::Migration[7.0] def change add_check_constraint :users, "price > 0", name: "price_check", validate: false end end ``` Then validate them in a separate migration. ```ruby -class ValidateCheckConstraint < ActiveRecord::Migration[6.1] +class ValidateCheckConstraint < ActiveRecord::Migration[7.0] def change validate_check_constraint :users, name: "price_check" end end ``` @@ -359,11 +359,11 @@ #### Bad Setting `NOT NULL` on an existing column blocks reads and writes while every row is checked. ```ruby -class SetSomeColumnNotNull < ActiveRecord::Migration[6.1] +class SetSomeColumnNotNull < ActiveRecord::Migration[7.0] def change change_column_null :users, :some_column, false end end ``` @@ -373,11 +373,11 @@ Instead, add a check constraint. For Rails 6.1, use: ```ruby -class SetSomeColumnNotNull < ActiveRecord::Migration[6.1] +class SetSomeColumnNotNull < ActiveRecord::Migration[7.0] def change add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false end end ``` @@ -397,11 +397,11 @@ 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. For Rails 6.1, use: ```ruby -class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.1] +class ValidateSomeColumnNotNull < ActiveRecord::Migration[7.0] def change validate_check_constraint :users, name: "users_some_column_null" # in Postgres 12+, you can then safely set NOT NULL on the column change_column_null :users, :some_column, false @@ -435,11 +435,11 @@ ### Executing SQL directly Strong Migrations can’t ensure safety for raw SQL statements. Make really sure that what you’re doing is safe, then use: ```ruby -class ExecuteSQL < ActiveRecord::Migration[6.1] +class ExecuteSQL < ActiveRecord::Migration[7.0] def change safety_assured { execute "..." } end end ``` @@ -451,11 +451,11 @@ #### Bad In Postgres, adding an index non-concurrently blocks writes. ```ruby -class AddSomeIndexToUsers < ActiveRecord::Migration[6.1] +class AddSomeIndexToUsers < ActiveRecord::Migration[7.0] def change add_index :users, :some_column end end ``` @@ -463,11 +463,11 @@ #### Good Add indexes concurrently. ```ruby -class AddSomeIndexToUsers < ActiveRecord::Migration[6.1] +class AddSomeIndexToUsers < ActiveRecord::Migration[7.0] disable_ddl_transaction! def change add_index :users, :some_column, algorithm: :concurrently end @@ -489,11 +489,11 @@ #### Bad Rails adds an index non-concurrently to references by default, which blocks writes in Postgres. ```ruby -class AddReferenceToUsers < ActiveRecord::Migration[6.1] +class AddReferenceToUsers < ActiveRecord::Migration[7.0] def change add_reference :users, :city end end ``` @@ -501,11 +501,11 @@ #### Good Make sure the index is added concurrently. ```ruby -class AddReferenceToUsers < ActiveRecord::Migration[6.1] +class AddReferenceToUsers < ActiveRecord::Migration[7.0] disable_ddl_transaction! def change add_reference :users, :city, index: {algorithm: :concurrently} end @@ -519,21 +519,21 @@ #### Bad In Postgres, adding a foreign key blocks writes on both tables. ```ruby -class AddForeignKeyOnUsers < ActiveRecord::Migration[6.1] +class AddForeignKeyOnUsers < ActiveRecord::Migration[7.0] def change add_foreign_key :users, :orders end end ``` or ```ruby -class AddReferenceToUsers < ActiveRecord::Migration[6.1] +class AddReferenceToUsers < ActiveRecord::Migration[7.0] def change add_reference :users, :order, foreign_key: true end end ``` @@ -543,21 +543,21 @@ Add the foreign key without validating existing rows, then validate them in a separate migration. For Rails 5.2+, use: ```ruby -class AddForeignKeyOnUsers < ActiveRecord::Migration[6.1] +class AddForeignKeyOnUsers < ActiveRecord::Migration[7.0] def change add_foreign_key :users, :orders, validate: false end end ``` Then: ```ruby -class ValidateForeignKeyOnUsers < ActiveRecord::Migration[6.1] +class ValidateForeignKeyOnUsers < ActiveRecord::Migration[7.0] def change validate_foreign_key :users, :orders end end ``` @@ -591,11 +591,11 @@ #### Bad In Postgres, there’s no equality operator for the `json` column type, which can cause errors for existing `SELECT DISTINCT` queries in your application. ```ruby -class AddPropertiesToUsers < ActiveRecord::Migration[6.1] +class AddPropertiesToUsers < ActiveRecord::Migration[7.0] def change add_column :users, :properties, :json end end ``` @@ -603,11 +603,11 @@ #### Good Use `jsonb` instead. ```ruby -class AddPropertiesToUsers < ActiveRecord::Migration[6.1] +class AddPropertiesToUsers < ActiveRecord::Migration[7.0] def change add_column :users, :properties, :jsonb end end ``` @@ -617,11 +617,11 @@ #### Bad Adding a non-unique index with more than three columns rarely improves performance. ```ruby -class AddSomeIndexToUsers < ActiveRecord::Migration[6.1] +class AddSomeIndexToUsers < ActiveRecord::Migration[7.0] def change add_index :users, [:a, :b, :c, :d] end end ``` @@ -629,11 +629,11 @@ #### Good Instead, start an index with columns that narrow down the results the most. ```ruby -class AddSomeIndexToUsers < ActiveRecord::Migration[6.1] +class AddSomeIndexToUsers < ActiveRecord::Migration[7.0] def change add_index :users, [:b, :d] end end ``` @@ -643,10 +643,10 @@ ## Assuring Safety 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. ```ruby -class MySafeMigration < ActiveRecord::Migration[6.1] +class MySafeMigration < ActiveRecord::Migration[7.0] def change safety_assured { remove_column :users, :some_column } end end ```