README.md in strong_migrations-0.2.3 vs README.md in strong_migrations-0.3.0
- old
+ new
@@ -14,30 +14,29 @@
gem 'strong_migrations'
```
## How It Works
-Strong Migrations detects potentially dangerous operations in migrations, prevents them from running by default, and provides instructions on safer ways to do what you want.
+Strong Migrations detects potentially dangerous operations in migrations, prevents them from running by default, and provides instructions on safer ways to do what you want. Here’s an example:
```
- __ __ _____ _______ _
- \ \ / /\ |_ _|__ __| |
- \ \ /\ / / \ | | | | | |
- \ \/ \/ / /\ \ | | | | | |
- \ /\ / ____ \ _| |_ | | |_|
- \/ \/_/ \_\_____| |_| (_) #strong_migrations
+=== Dangerous operation detected #strong_migrations ===
ActiveRecord caches attributes which causes problems
when removing columns. Be sure to ignore the column:
class User < ApplicationRecord
- self.ignored_columns = %w(some_column)
+ self.ignored_columns = ["some_column"]
end
-Once that's deployed, wrap this step in a safety_assured { ... } block.
+Deploy the code, then wrap this step in a safety_assured { ... } block.
-More info: https://github.com/ankane/strong_migrations#removing-a-column
+class RemoveColumn < ActiveRecord::Migration[5.2]
+ def change
+ safety_assured { remove_column :users, :some_column }
+ end
+end
```
## Dangerous Operations
The following operations can cause downtime or errors:
@@ -46,10 +45,11 @@
- removing a column
- changing the type of a column
- setting a `NOT NULL` constraint with a default value
- renaming a column
- renaming a table
+- creating a table with the `force` option
- adding an index non-concurrently (Postgres only)
- adding a `json` column to an existing table (Postgres only)
Also checks for best practices:
@@ -62,11 +62,11 @@
Adding a column with a non-null default causes the entire table to be rewritten.
Instead, add the column without a default value, then change the default.
```ruby
-class AddSomeColumnToUsers < ActiveRecord::Migration[5.1]
+class AddSomeColumnToUsers < ActiveRecord::Migration[5.2]
def up
add_column :users, :some_column, :text
change_column_default :users, :some_column, "default_value"
end
@@ -76,25 +76,27 @@
end
```
Don’t backfill existing rows in this migration, as it can cause downtime. See the next section for how to do it safely.
+> With Postgres, this operation is safe as of Postgres 11
+
### Backfilling data
To backfill data, use the Rails console or a separate migration with `disable_ddl_transaction!`. Avoid backfilling in a transaction, especially one that alters a table. See [this great article](https://wework.github.io/data/2015/11/05/add-columns-with-default-values-to-large-tables-in-rails-postgres/) on why.
```ruby
-class BackfillSomeColumn < ActiveRecord::Migration[5.1]
+class BackfillSomeColumn < ActiveRecord::Migration[5.2]
disable_ddl_transaction!
def change
# Rails 5+
User.in_batches.update_all some_column: "default_value"
# Rails < 5
- User.find_in_batches do |users|
- User.where(id: users.map(&:id)).update_all some_column: "default_value"
+ User.find_in_batches do |records|
+ User.where(id: records.map(&:id)).update_all some_column: "default_value"
end
end
end
```
@@ -105,11 +107,11 @@
1. Tell ActiveRecord to ignore the column from its cache
```ruby
# For Rails 5+
class User < ApplicationRecord
- self.ignored_columns = %w(some_column)
+ self.ignored_columns = ["some_column"]
end
# For Rails < 5
class User < ActiveRecord::Base
def self.columns
@@ -120,22 +122,22 @@
2. Deploy code
3. Write a migration to remove the column (wrap in `safety_assured` block)
```ruby
- class RemoveSomeColumnFromUsers < ActiveRecord::Migration[5.1]
+ class RemoveSomeColumnFromUsers < ActiveRecord::Migration[5.2]
def change
safety_assured { remove_column :users, :some_column }
end
end
```
4. Deploy and run migration
### Renaming or changing the type of a column
-If you really have to:
+A safer approach is to:
1. Create a new column
2. Write to both columns
3. Backfill data from the old column to the new column
4. Move reads from the old column to the new column
@@ -144,11 +146,11 @@
One exception is changing a `varchar` column to `text`, which is safe in Postgres 9.1+.
### Renaming a table
-If you really have to:
+A safer approach is to:
1. Create a new table
2. Write to both tables
3. Backfill data from the old table to new table
4. Move reads from the old table to the new table
@@ -158,23 +160,36 @@
### Adding an index (Postgres)
Add indexes concurrently.
```ruby
-class AddSomeIndexToUsers < ActiveRecord::Migration[5.1]
+class AddSomeIndexToUsers < ActiveRecord::Migration[5.2]
disable_ddl_transaction!
def change
- add_index :users, :some_index, algorithm: :concurrently
+ add_index :users, :some_column, algorithm: :concurrently
end
end
```
-If you forget `disable_ddl_transaction!`, the migration will fail. Also, note that indexes on new tables (those created in the same migration) don’t require this.
+If you forget `disable_ddl_transaction!`, the migration will fail. Also, note that indexes on new tables (those created in the same migration) don’t require this. Check out [gindex](https://github.com/ankane/gindex) to quickly generate index migrations without memorizing the syntax.
-Check out [this gem](https://github.com/ankane/gindex) to quickly generate index migrations without memorizing the syntax.
+Rails 5+ adds an index to references by default. To make sure this happens concurrently, use:
+```ruby
+class AddSomeReferenceToUsers < ActiveRecord::Migration[5.2]
+ disable_ddl_transaction!
+
+ def change
+ add_reference :users, :reference, index: false
+ add_index :users, :reference_id, algorithm: :concurrently
+ end
+end
+```
+
+For polymorphic references, add a compound index on type and id.
+
### Adding a json column (Postgres)
There’s no equality operator for the `json` column type, which causes issues for `SELECT DISTINCT` queries.
If you’re on Postgres 9.4+, use `jsonb` instead.
@@ -188,11 +203,11 @@
```
Then add the column:
```ruby
-class AddJsonColumnToUsers < ActiveRecord::Migration[5.1]
+class AddJsonColumnToUsers < ActiveRecord::Migration[5.2]
def change
safety_assured { add_column :users, :some_column, :json }
end
end
```
@@ -200,17 +215,31 @@
## Assuring Safety
To mark a step in the migration as safe, despite using method that might otherwise be dangerous, wrap it in a `safety_assured` block.
```ruby
-class MySafeMigration < ActiveRecord::Migration[5.1]
+class MySafeMigration < ActiveRecord::Migration[5.2]
def change
safety_assured { remove_column :users, :some_column }
end
end
```
+## Custom Checks
+
+Add your own custom checks with:
+
+```ruby
+StrongMigrations.add_check do |method, args|
+ if method == :add_index && args[0].to_s == "users"
+ stop! "No more indexes on the users table"
+ end
+end
+```
+
+Use the `stop!` method to stop migrations.
+
## Existing Migrations
To mark migrations as safe that were created before installing this gem, create an initializer with:
```ruby
@@ -272,10 +301,10 @@
There’s also [a gem](https://github.com/gocardless/activerecord-safer_migrations) you can use for this.
## Bigint Primary Keys (Postgres & MySQL)
-Rails 5.1+ uses `bigint` for primary keys to keep you from running out of ids. To get this in earlier versions of Rails, check out [this gem](https://github.com/Shopify/rails-bigint-primarykey).
+Rails 5.1+ uses `bigint` for primary keys to keep you from running out of ids. To get this in earlier versions of Rails, check out [rails-bigint-primarykey](https://github.com/Shopify/rails-bigint-primarykey).
## Additional Reading
- [Rails Migrations with No Downtime](https://pedro.herokuapp.com/past/2011/7/13/rails_migrations_with_no_downtime/)
- [Safe Operations For High Volume PostgreSQL](https://www.braintreepayments.com/blog/safe-operations-for-high-volume-postgresql/)