README.md in strong_migrations-0.7.4 vs README.md in strong_migrations-0.7.5
- 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.0]
+class RemoveColumn < ActiveRecord::Migration[6.1]
def change
safety_assured { remove_column :users, :name }
end
end
```
@@ -64,10 +64,11 @@
- [backfilling data](#backfilling-data)
- [changing the type of a column](#changing-the-type-of-a-column)
- [renaming a column](#renaming-a-column)
- [renaming a table](#renaming-a-table)
- [creating a table with the force option](#creating-a-table-with-the-force-option)
+- [adding a check constraint](#adding-a-check-constraint)
- [setting NOT NULL on an existing column](#setting-not-null-on-an-existing-column)
- [executing SQL directly](#executing-SQL-directly)
Postgres-specific checks:
@@ -87,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.0]
+class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.1]
def change
remove_column :users, :some_column
end
end
```
@@ -108,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.0]
+ class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.1]
def change
safety_assured { remove_column :users, :some_column }
end
end
```
@@ -124,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.0]
+class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
def change
add_column :users, :some_column, :text, default: "default_value"
end
end
```
@@ -138,11 +139,11 @@
#### Good
Instead, add the column without a default value, then change the default.
```ruby
-class AddSomeColumnToUsers < ActiveRecord::Migration[6.0]
+class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
def up
add_column :users, :some_column, :text
change_column_default :users, :some_column, "default_value"
end
@@ -159,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.0]
+class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
def change
add_column :users, :some_column, :text
User.update_all some_column: "default_value"
end
end
@@ -174,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.0]
+class BackfillSomeColumn < ActiveRecord::Migration[6.1]
disable_ddl_transaction!
def up
User.unscoped.in_batches do |relation|
relation.update_all some_column: "default_value"
@@ -193,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.0]
+class ChangeSomeColumnType < ActiveRecord::Migration[6.1]
def change
change_column :users, :some_column, :new_type
end
end
```
@@ -232,11 +233,11 @@
#### Bad
Renaming a column that’s in use will cause errors in your application.
```ruby
-class RenameSomeColumn < ActiveRecord::Migration[6.0]
+class RenameSomeColumn < ActiveRecord::Migration[6.1]
def change
rename_column :users, :some_column, :new_name
end
end
```
@@ -257,11 +258,11 @@
#### Bad
Renaming a table that’s in use will cause errors in your application.
```ruby
-class RenameUsersToCustomers < ActiveRecord::Migration[6.0]
+class RenameUsersToCustomers < ActiveRecord::Migration[6.1]
def change
rename_table :users, :customers
end
end
```
@@ -282,11 +283,11 @@
#### Bad
The `force` option can drop an existing table.
```ruby
-class CreateUsers < ActiveRecord::Migration[6.0]
+class CreateUsers < ActiveRecord::Migration[6.1]
def change
create_table :users, force: true do |t|
# ...
end
end
@@ -296,54 +297,124 @@
#### Good
Create tables without the `force` option.
```ruby
-class CreateUsers < ActiveRecord::Migration[6.0]
+class CreateUsers < ActiveRecord::Migration[6.1]
def change
create_table :users do |t|
# ...
end
end
end
```
If you intend to drop an existing table, run `drop_table` first.
+### Adding a check constraint
+
+:turtle: Safe by default available
+
+#### 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]
+ def change
+ add_check_constraint :users, "price > 0", name: "price_check"
+ end
+end
+```
+
+#### Good - Postgres
+
+Add the check constraint without validating existing rows:
+
+```ruby
+class AddCheckConstraint < ActiveRecord::Migration[6.1]
+ 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]
+ def change
+ validate_check_constraint :users, name: "price_check"
+ end
+end
+```
+
+#### Good - MySQL and MariaDB
+
+[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).
+
### Setting NOT NULL on an existing column
:turtle: Safe by default available
#### Bad
Setting `NOT NULL` on an existing column blocks reads and writes while every row is checked.
```ruby
-class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
+class SetSomeColumnNotNull < ActiveRecord::Migration[6.1]
def change
change_column_null :users, :some_column, false
end
end
```
#### Good - Postgres
-Instead, add a check constraint:
+Instead, add a check constraint.
+For Rails 6.1, use:
+
```ruby
+class SetSomeColumnNotNull < ActiveRecord::Migration[6.1]
+ def change
+ add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
+ end
+end
+```
+
+For Rails < 6.1, use:
+
+```ruby
class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
def change
safety_assured do
execute 'ALTER TABLE "users" ADD CONSTRAINT "users_some_column_null" CHECK ("some_column" IS NOT NULL) NOT VALID'
end
end
end
```
-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 Postgres 12+, once the check constraint is validated, you can safely set `NOT NULL` on the column and drop the check constraint.
+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]
+ 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
+ remove_check_constraint :users, name: "users_some_column_null"
+ end
+end
+```
+
+For Rails < 6.1, use:
+
+```ruby
class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
def change
safety_assured do
execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "users_some_column_null"'
end
@@ -364,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.0]
+class ExecuteSQL < ActiveRecord::Migration[6.1]
def change
safety_assured { execute "..." }
end
end
```
@@ -380,11 +451,11 @@
#### Bad
In Postgres, adding an index non-concurrently blocks writes.
```ruby
-class AddSomeIndexToUsers < ActiveRecord::Migration[6.0]
+class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
def change
add_index :users, :some_column
end
end
```
@@ -392,11 +463,11 @@
#### Good
Add indexes concurrently.
```ruby
-class AddSomeIndexToUsers < ActiveRecord::Migration[6.0]
+class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
disable_ddl_transaction!
def change
add_index :users, :some_column, algorithm: :concurrently
end
@@ -418,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.0]
+class AddReferenceToUsers < ActiveRecord::Migration[6.1]
def change
add_reference :users, :city
end
end
```
@@ -430,11 +501,11 @@
#### Good
Make sure the index is added concurrently.
```ruby
-class AddReferenceToUsers < ActiveRecord::Migration[6.0]
+class AddReferenceToUsers < ActiveRecord::Migration[6.1]
disable_ddl_transaction!
def change
add_reference :users, :city, index: {algorithm: :concurrently}
end
@@ -448,21 +519,21 @@
#### Bad
In Postgres, adding a foreign key blocks writes on both tables.
```ruby
-class AddForeignKeyOnUsers < ActiveRecord::Migration[6.0]
+class AddForeignKeyOnUsers < ActiveRecord::Migration[6.1]
def change
add_foreign_key :users, :orders
end
end
```
or
```ruby
-class AddReferenceToUsers < ActiveRecord::Migration[6.0]
+class AddReferenceToUsers < ActiveRecord::Migration[6.1]
def change
add_reference :users, :order, foreign_key: true
end
end
```
@@ -472,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.0]
+class AddForeignKeyOnUsers < ActiveRecord::Migration[6.1]
def change
add_foreign_key :users, :orders, validate: false
end
end
```
Then:
```ruby
-class ValidateForeignKeyOnUsers < ActiveRecord::Migration[6.0]
+class ValidateForeignKeyOnUsers < ActiveRecord::Migration[6.1]
def change
validate_foreign_key :users, :orders
end
end
```
@@ -520,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.0]
+class AddPropertiesToUsers < ActiveRecord::Migration[6.1]
def change
add_column :users, :properties, :json
end
end
```
@@ -532,11 +603,11 @@
#### Good
Use `jsonb` instead.
```ruby
-class AddPropertiesToUsers < ActiveRecord::Migration[6.0]
+class AddPropertiesToUsers < ActiveRecord::Migration[6.1]
def change
add_column :users, :properties, :jsonb
end
end
```
@@ -546,11 +617,11 @@
#### Bad
Adding a non-unique index with more than three columns rarely improves performance.
```ruby
-class AddSomeIndexToUsers < ActiveRecord::Migration[6.0]
+class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
def change
add_index :users, [:a, :b, :c, :d]
end
end
```
@@ -558,11 +629,11 @@
#### Good
Instead, start an index with columns that narrow down the results the most.
```ruby
-class AddSomeIndexToUsers < ActiveRecord::Migration[6.0]
+class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
def change
add_index :users, [:b, :d]
end
end
```
@@ -572,11 +643,11 @@
## 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.0]
+class MySafeMigration < ActiveRecord::Migration[6.1]
def change
safety_assured { remove_column :users, :some_column }
end
end
```
@@ -589,9 +660,10 @@
Make operations safe by default.
- adding and removing an index
- adding a foreign key
+- adding a check constraint
- setting NOT NULL on an existing column
Add to `config/initializers/strong_migrations.rb`:
```ruby