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/)