### Clone and Install Strong Migrations Source: https://github.com/ankane/strong_migrations/blob/master/README.md Clone the repository, navigate to the directory, and install the necessary Ruby gems to get started with development. ```sh git clone https://github.com/ankane/strong_migrations.git cd strong_migrations bundle install ``` -------------------------------- ### Install Strong Migrations Gem Source: https://github.com/ankane/strong_migrations/blob/master/README.md Add the gem to your Gemfile and run bundle install. ```ruby gem "strong_migrations" ``` -------------------------------- ### Create Table with Force Option Source: https://github.com/ankane/strong_migrations/blob/master/README.md Avoid accidentally dropping an existing table when creating a new one. This example shows the 'bad' approach using `force: true`. ```ruby class CreateUsers < ActiveRecord::Migration[8.1] def change create_table :users, force: true do |t| # ... end end end ``` -------------------------------- ### Add Foreign Key Safely Source: https://github.com/ankane/strong_migrations/blob/master/README.md Adding a foreign key can block writes on both tables. This example shows the 'bad' approach. ```ruby class AddForeignKeyOnUsers < ActiveRecord::Migration[8.1] def change add_foreign_key :users, :orders end end ``` ```ruby class AddReferenceToUsers < ActiveRecord::Migration[8.1] def change add_reference :users, :order, foreign_key: true end end ``` -------------------------------- ### Change Column Type Safely Source: https://github.com/ankane/strong_migrations/blob/master/README.md Avoid table rewrites when changing column types. This example shows the 'bad' approach that can cause downtime. ```ruby class ChangeSomeColumnType < ActiveRecord::Migration[8.1] def change change_column :users, :some_column, :new_type end end ``` -------------------------------- ### Handle Dangerous Operations with safety_assured Source: https://github.com/ankane/strong_migrations/blob/master/README.md When a dangerous operation is detected, wrap it in a `safety_assured` block within your migration. This example shows how to safely remove a column. ```txt === Dangerous operation detected #strong_migrations === Active Record caches attributes, which causes problems when removing columns. Be sure to ignore the column: class User < ApplicationRecord self.ignored_columns += ["name"] end Deploy the code, then wrap this step in a safety_assured { ... } block. class RemoveColumn < ActiveRecord::Migration[8.1] def change safety_assured { remove_column :users, :name } end end ``` ```ruby class RemoveColumn < ActiveRecord::Migration[8.1] def change safety_assured { remove_column :users, :name } end end ``` -------------------------------- ### Rename Table Safely Source: https://github.com/ankane/strong_migrations/blob/master/README.md Avoid application errors when renaming tables. This example shows the 'bad' approach that can cause downtime. ```ruby class RenameUsersToCustomers < ActiveRecord::Migration[8.1] def change rename_table :users, :customers end end ``` -------------------------------- ### Rename Column Safely Source: https://github.com/ankane/strong_migrations/blob/master/README.md Avoid application errors when renaming columns. This example shows the 'bad' approach that can cause downtime. ```ruby class RenameSomeColumn < ActiveRecord::Migration[8.1] def change rename_column :users, :some_column, :new_name end end ``` -------------------------------- ### Add Exclusion Constraint Safely Source: https://github.com/ankane/strong_migrations/blob/master/README.md Adding an exclusion constraint directly can block reads and writes. The 'good' example notes that exclusion constraints cannot be marked `NOT VALID` and asks for community input on safe methods. ```ruby class AddExclusionConstraint < ActiveRecord::Migration[8.1] def change add_exclusion_constraint :users, "number WITH =", using: :gist end end ``` -------------------------------- ### Limit non-unique indexes to three columns Source: https://github.com/ankane/strong_migrations/blob/master/README.md Adding a non-unique index with more than three columns rarely improves performance. Start an index with columns that narrow down the results the most. ```ruby class AddSomeIndexToUsers < ActiveRecord::Migration[8.1] def change add_index :users, [:a, :b, :c, :d] end end ``` ```ruby class AddSomeIndexToUsers < ActiveRecord::Migration[8.1] def change add_index :users, [:d, :b] end end ``` -------------------------------- ### Add Auto-Incrementing Column Safely Source: https://github.com/ankane/strong_migrations/blob/master/README.md Avoid table rewrites when adding an auto-incrementing column. This example shows the 'bad' approach that can cause downtime. ```ruby class AddIdToCitiesUsers < ActiveRecord::Migration[8.1] def change add_column :cities_users, :id, :primary_key end end ``` -------------------------------- ### Add Stored Generated Column Safely Source: https://github.com/ankane/strong_migrations/blob/master/README.md Avoid table rewrites when adding a stored generated column. This example shows the 'bad' approach that can cause downtime. ```ruby class AddSomeColumnToUsers < ActiveRecord::Migration[8.1] def change add_column :users, :some_column, :virtual, type: :string, as: "...", stored: true end end ``` -------------------------------- ### Run Project Tests Source: https://github.com/ankane/strong_migrations/blob/master/CONTRIBUTING.md Execute the test suite to ensure your changes are compatible with the existing codebase. This command should be run after making any modifications. ```sh bundle exec rake test ``` -------------------------------- ### Run Tests for MySQL and MariaDB Source: https://github.com/ankane/strong_migrations/blob/master/README.md Set up the MySQL or MariaDB test database and run the test suite using Rake, specifying the adapter. This command is used for testing with MySQL and MariaDB. ```sh mysqladmin create strong_migrations_test ADAPTER=mysql2 bundle exec rake test ``` -------------------------------- ### Generate Index Migration (Shell) Source: https://github.com/ankane/strong_migrations/blob/master/README.md Use the `gindex` gem to instantly generate an index migration. ```sh rails g index table column ``` -------------------------------- ### Run Tests for PostgreSQL Source: https://github.com/ankane/strong_migrations/blob/master/README.md Set up the PostgreSQL test database and run the test suite using Rake. This is the standard procedure for testing the gem with PostgreSQL. ```sh createdb strong_migrations_test bundle exec rake test ``` -------------------------------- ### Generate Strong Migrations Configuration Source: https://github.com/ankane/strong_migrations/blob/master/README.md Run the generator to set up Strong Migrations in your Rails application. ```sh bundle install rails generate strong_migrations:install ``` -------------------------------- ### Set Target Database Versions for Multiple Databases Source: https://github.com/ankane/strong_migrations/blob/master/README.md Configure target database versions for multiple databases in development and test environments, specifying versions for each named database. ```ruby StrongMigrations.target_version = {primary: 16, catalog: 18} ``` -------------------------------- ### Create Table Safely Source: https://github.com/ankane/strong_migrations/blob/master/README.md Create tables without the `force` option to prevent accidental data loss. If dropping an existing table is intended, use `drop_table` first. ```ruby class CreateUsers < ActiveRecord::Migration[8.1] def change create_table :users do |t| # ... end end end ``` -------------------------------- ### Set Migration Lock and Statement Timeouts Source: https://github.com/ankane/strong_migrations/blob/master/README.md Configure lock and statement timeouts for migrations by creating an initializer. Alternatively, set these directly on the database user for Postgres. ```ruby StrongMigrations.lock_timeout = 10.seconds StrongMigrations.statement_timeout = 1.hour ``` ```sql ALTER ROLE myuser SET lock_timeout = '10s'; ALTER ROLE myuser SET statement_timeout = '1h'; ``` -------------------------------- ### Avoid `COPY` algorithm for MySQL/MariaDB indexes Source: https://github.com/ankane/strong_migrations/blob/master/README.md In MySQL and MariaDB, using the `COPY` algorithm for `add_index` blocks writes. Use the default algorithm instead. ```ruby class AddSomeIndexToUsers < ActiveRecord::Migration[8.1] def change add_index :users, :some_column, algorithm: :copy end end ``` ```ruby class AddSomeIndexToUsers < ActiveRecord::Migration[8.1] def change add_index :users, :some_column end end ``` -------------------------------- ### Execute Raw SQL Safely Source: https://github.com/ankane/strong_migrations/blob/master/README.md Use `safety_assured` to execute raw SQL statements when Strong Migrations cannot guarantee safety. Ensure the SQL statement is safe before execution. ```ruby class ExecuteSQL < ActiveRecord::Migration[8.1] def change safety_assured { execute "..." } end end ``` -------------------------------- ### Enable Automatic Table Analysis Source: https://github.com/ankane/strong_migrations/blob/master/README.md Automatically analyze tables to update planner statistics after an index is added during migrations. ```ruby StrongMigrations.auto_analyze = true ``` -------------------------------- ### Configure App Database Connection Timeouts (MariaDB) Source: https://github.com/ankane/strong_migrations/blob/master/README.md Set connection, read, write, statement, and lock timeouts in `config/database.yml` for MariaDB applications. ```yaml production: connect_timeout: 5 read_timeout: 5 write_timeout: 5 variables: max_statement_time: 15 # sec lock_wait_timeout: 10 # sec ``` -------------------------------- ### Configure App Database Connection Timeouts (MySQL) Source: https://github.com/ankane/strong_migrations/blob/master/README.md Set connection, read, write, execution, and lock timeouts in `config/database.yml` for MySQL applications. ```yaml production: connect_timeout: 5 read_timeout: 5 write_timeout: 5 variables: max_execution_time: 15000 # ms lock_wait_timeout: 10 # sec ``` -------------------------------- ### Alphabetize Schema File Columns Source: https://github.com/ankane/strong_migrations/blob/master/README.md Ensure consistent column order in `db/schema.rb` by enabling alphabetization. This helps prevent merge conflicts when multiple developers work on the project. ```ruby StrongMigrations.alphabetize_schema = true ``` -------------------------------- ### Set Target Database Version for Development Source: https://github.com/ankane/strong_migrations/blob/master/README.md Specify the production database version for development and test environments to ensure accurate checks. This option only affects development and test environments. ```ruby StrongMigrations.target_version = 16 ``` -------------------------------- ### Configure strong_migrations to be safe by default Source: https://github.com/ankane/strong_migrations/blob/master/README.md Set `StrongMigrations.safe_by_default = true` in an initializer to make certain operations safe by default. This allows writing 'bad' code which will be performed as the 'good' version. ```ruby StrongMigrations.safe_by_default = true ``` -------------------------------- ### Add custom migration checks Source: https://github.com/ankane/strong_migrations/blob/master/README.md Use `StrongMigrations.add_check` to define custom checks for migration methods and arguments. Use the `stop!` method to halt migrations with a custom message. ```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 ``` -------------------------------- ### Add Strong Migrations Gem Source: https://github.com/ankane/strong_migrations/blob/master/CONTRIBUTING.md Include this gem in your Gemfile to use strong_migrations in your project. ```ruby gem "strong_migrations", github: "ankane/strong_migrations" ``` -------------------------------- ### Safe Backfilling Data in Batches Source: https://github.com/ankane/strong_migrations/blob/master/README.md Backfill data safely by disabling DDL transactions, processing in batches, and throttling. This prevents long-running locks on tables. ```ruby class BackfillSomeColumn < ActiveRecord::Migration[8.1] disable_ddl_transaction! def up User.unscoped.in_batches(of: 10000) do |relation| relation.where(some_column: nil).update_all some_column: "default_value" sleep(0.01) # throttle end end end ``` -------------------------------- ### Configure App Database Connection Timeouts (Postgres) Source: https://github.com/ankane/strong_migrations/blob/master/README.md Set connection, statement, and lock timeouts in `config/database.yml` for Postgres applications. Note that PgBouncer in transaction mode requires setting these on the database user. ```yaml production: connect_timeout: 5 variables: statement_timeout: 15s lock_timeout: 10s ``` -------------------------------- ### Skip checks for specific databases Source: https://github.com/ankane/strong_migrations/blob/master/README.md Use `StrongMigrations.skip_database` to skip checks and other functionality for particular database types. This setting does not affect `alphabetize_schema`. ```ruby StrongMigrations.skip_database(:catalog) ``` -------------------------------- ### Mark Existing Migrations as Safe Source: https://github.com/ankane/strong_migrations/blob/master/README.md Specify a timestamp to mark all migrations created before this point as safe, preventing strong_migrations from checking them. Use the version from your latest migration. ```ruby StrongMigrations.start_after = 20250101000000 ``` -------------------------------- ### Add Index Concurrently (Postgres) Source: https://github.com/ankane/strong_migrations/blob/master/README.md Add an index to a table concurrently to avoid blocking writes. This requires `disable_ddl_transaction!` for migrations that are not on new tables. ```ruby class AddSomeIndexToUsers < ActiveRecord::Migration[8.1] disable_ddl_transaction! def change add_index :users, :some_column, algorithm: :concurrently end end ``` -------------------------------- ### Add Index Non-concurrently (Postgres - Bad) Source: https://github.com/ankane/strong_migrations/blob/master/README.md Adding an index non-concurrently in Postgres blocks writes. ```ruby class AddSomeIndexToUsers < ActiveRecord::Migration[8.1] def change add_index :users, :some_column end end ``` -------------------------------- ### Set NOT NULL Safely with Check Constraint Source: https://github.com/ankane/strong_migrations/blob/master/README.md Instead of directly setting `NOT NULL`, add a check constraint first. Validate the constraint and then set `NOT NULL` in a separate migration to avoid blocking reads and writes. ```ruby class SetSomeColumnNotNull < ActiveRecord::Migration[8.1] def change change_column_null :users, :some_column, false end end ``` ```ruby class SetSomeColumnNotNull < ActiveRecord::Migration[8.1] def change add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false end end ``` ```ruby class ValidateSomeColumnNotNull < ActiveRecord::Migration[8.1] def up validate_check_constraint :users, name: "users_some_column_null" change_column_null :users, :some_column, false remove_check_constraint :users, name: "users_some_column_null" end def down add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false change_column_null :users, :some_column, true end end ``` -------------------------------- ### Optimize Schema Dumping for Faster Migrations Source: https://github.com/ankane/strong_migrations/blob/master/README.md Configure the Rakefile to only dump the schema when adding a new migration and if Git detects changes in the migration directory. This speeds up the migration process in development. ```ruby task :faster_migrations do ActiveRecord.dump_schema_after_migration = Rails.env.development? && `git status db/migrate/ --porcelain`.present? end task "db:migrate" => "faster_migrations" ``` -------------------------------- ### Add Unique Constraint Safely Source: https://github.com/ankane/strong_migrations/blob/master/README.md Avoid blocking reads and writes by creating a unique index concurrently before adding the unique constraint. ```ruby class AddUniqueConstraint < ActiveRecord::Migration[8.1] def change add_unique_constraint :users, :some_column end end ``` ```ruby class AddUniqueConstraint < ActiveRecord::Migration[8.1] disable_ddl_transaction! def up add_index :users, :some_column, unique: true, algorithm: :concurrently add_unique_constraint :users, using_index: "index_users_on_some_column" end def down remove_unique_constraint :users, :some_column end end ``` -------------------------------- ### Remove Column - Good Practice Step 3: Safe Removal Source: https://github.com/ankane/strong_migrations/blob/master/README.md After deploying the code that ignores the column, write the migration to safely remove the column using `safety_assured`. This is the third step in the 'good' practice. ```ruby class RemoveSomeColumnFromUsers < ActiveRecord::Migration[8.1] def change safety_assured { remove_column :users, :some_column } end end ``` -------------------------------- ### Customize error messages for migration checks Source: https://github.com/ankane/strong_migrations/blob/master/README.md Customize specific error messages by assigning a new string to the corresponding key in `StrongMigrations.error_messages`. Refer to the source code for a list of available keys. ```ruby StrongMigrations.error_messages[:add_column_default] = "Your custom instructions" ``` -------------------------------- ### Enable opt-in checks for specific operations Source: https://github.com/ankane/strong_migrations/blob/master/README.md Enable specific checks that are not enabled by default, such as `remove_index` for non-concurrent removal in Postgres. ```ruby StrongMigrations.enable_check(:remove_index) ``` -------------------------------- ### Add Foreign Key (Postgres - Bad) Source: https://github.com/ankane/strong_migrations/blob/master/README.md Adding a foreign key without `validate: false` can block writes on large tables during validation. ```ruby class AddForeignKeyOnUsers < ActiveRecord::Migration[8.1] def change add_foreign_key :users, :orders end end ``` -------------------------------- ### Remove Column - Good Practice Step 1: Ignore Column Source: https://github.com/ankane/strong_migrations/blob/master/README.md Before removing a column, instruct Active Record to ignore it from its cache by adding it to `ignored_columns`. This is the first step in the 'good' practice. ```ruby class User < ApplicationRecord self.ignored_columns += ["some_column"] end ``` -------------------------------- ### Add Reference Concurrently (Postgres) Source: https://github.com/ankane/strong_migrations/blob/master/README.md Add a reference to a table concurrently to avoid blocking writes. This requires `disable_ddl_transaction!` and specifying `index: {algorithm: :concurrently}`. ```ruby class AddReferenceToUsers < ActiveRecord::Migration[8.1] disable_ddl_transaction! def change add_reference :users, :city, index: {algorithm: :concurrently} end end ``` -------------------------------- ### Add Column with Volatile Default Safely Source: https://github.com/ankane/strong_migrations/blob/master/README.md Avoid rewriting the entire table by adding a column without a default value first, then changing the default in a subsequent step. Backfill data separately. ```ruby class AddSomeColumnToUsers < ActiveRecord::Migration[8.1] def change add_column :users, :some_column, :uuid, default: "gen_random_uuid()" end end ``` ```ruby class AddSomeColumnToUsers < ActiveRecord::Migration[8.1] def up add_column :users, :some_column, :uuid change_column_default :users, :some_column, "gen_random_uuid()" end def down remove_column :users, :some_column end end ``` -------------------------------- ### Add Reference (Postgres - Bad) Source: https://github.com/ankane/strong_migrations/blob/master/README.md Rails adds an index non-concurrently to references by default, which blocks writes in Postgres. ```ruby class AddReferenceToUsers < ActiveRecord::Migration[8.1] def change add_reference :users, :city end end ``` -------------------------------- ### Set Delay Between Lock Timeout Retries Source: https://github.com/ankane/strong_migrations/blob/master/README.md Configure the delay between automatic retries for statements when a lock timeout is reached during migrations. ```ruby StrongMigrations.lock_timeout_retry_delay = 10.seconds ``` -------------------------------- ### Configure Lock Timeout Retries Source: https://github.com/ankane/strong_migrations/blob/master/README.md Set the number of automatic retries for statements when a lock timeout is reached during migrations. This feature is experimental. ```ruby StrongMigrations.lock_timeout_retries = 3 ``` -------------------------------- ### Enable checks for down migrations Source: https://github.com/ankane/strong_migrations/blob/master/README.md By default, checks are disabled when migrating down. Set `StrongMigrations.check_down = true` to enable checks during rollbacks. ```ruby StrongMigrations.check_down = true ``` -------------------------------- ### Backfilling Data in a Single Transaction (Bad) Source: https://github.com/ankane/strong_migrations/blob/master/README.md Backfilling data within the same transaction that alters a table can keep the table locked for the duration of the backfill. ```ruby class AddSomeColumnToUsers < ActiveRecord::Migration[8.1] def change add_column :users, :some_column, :text User.update_all some_column: "default_value" end end ``` -------------------------------- ### Add JSONB Column Instead of JSON Source: https://github.com/ankane/strong_migrations/blob/master/README.md Use the `jsonb` column type instead of `json` to avoid potential errors with `SELECT DISTINCT` queries due to the lack of an equality operator for `json`. ```ruby class AddPropertiesToUsers < ActiveRecord::Migration[8.1] def change add_column :users, :properties, :json end end ``` ```ruby class AddPropertiesToUsers < ActiveRecord::Migration[8.1] def change add_column :users, :properties, :jsonb end end ``` -------------------------------- ### Avoid blocking writes/reads with index locking Source: https://github.com/ankane/strong_migrations/blob/master/README.md In MySQL and MariaDB, shared locking blocks writes, and exclusive locking blocks reads and writes. Use the default locking or no locking. ```ruby class AddSomeIndexToUsers < ActiveRecord::Migration[8.2] def change add_index :users, :some_column, lock: :shared end end ``` ```ruby class AddSomeIndexToUsers < ActiveRecord::Migration[8.2] def change add_index :users, :some_column end end ``` -------------------------------- ### Add Check Constraint (Postgres - Bad) Source: https://github.com/ankane/strong_migrations/blob/master/README.md Adding a check constraint without `validate: false` blocks reads and writes in Postgres while checking all rows. ```ruby class AddCheckConstraint < ActiveRecord::Migration[8.1] def change add_check_constraint :users, "price > 0", name: "price_check" end end ``` -------------------------------- ### Disable specific migration checks Source: https://github.com/ankane/strong_migrations/blob/master/README.md Disable specific checks using `StrongMigrations.disable_check` with the check's key. Refer to the source code for a list of available keys. ```ruby StrongMigrations.disable_check(:add_index) ``` -------------------------------- ### Assure safety for dangerous migration operations Source: https://github.com/ankane/strong_migrations/blob/master/README.md Wrap potentially dangerous operations like `remove_column` in a `safety_assured` block to mark the step as safe, even if the method might otherwise be dangerous. This is required for methods that cannot be inspected by the gem. ```ruby class MySafeMigration < ActiveRecord::Migration[8.1] def change safety_assured { remove_column :users, :some_column } end end ``` -------------------------------- ### Add Foreign Key Without Validation (Postgres) Source: https://github.com/ankane/strong_migrations/blob/master/README.md Add a foreign key to a table without validating existing rows. This is useful for large tables where validation can be time-consuming. Validate in a separate migration. ```ruby class AddForeignKeyOnUsers < ActiveRecord::Migration[8.1] def change add_foreign_key :users, :orders, validate: false end end ``` ```ruby class ValidateForeignKeyOnUsers < ActiveRecord::Migration[8.1] def change validate_foreign_key :users, :orders end end ``` -------------------------------- ### Avoid rewriting tables when adding columns with expression defaults Source: https://github.com/ankane/strong_migrations/blob/master/README.md In MySQL and MariaDB, adding a column with an expression default to an existing table rewrites the entire table, blocking writes. Add the column without a default first, then change the default. ```ruby class AddSomeColumnToUsers < ActiveRecord::Migration[8.1] def change add_column :users, :some_column, :datetime, default: -> { "(now())" } end end ``` ```ruby class AddSomeColumnToUsers < ActiveRecord::Migration[8.1] def up add_column :users, :some_column, :datetime change_column_default :users, :some_column, -> { "(now())" } end def down remove_column :users, :some_column end end ``` -------------------------------- ### Remove Column - Bad Practice Source: https://github.com/ankane/strong_migrations/blob/master/README.md Directly removing a column without considering Active Record's cache can lead to runtime exceptions. This is the 'bad' way. ```ruby class RemoveSomeColumnFromUsers < ActiveRecord::Migration[8.1] def change remove_column :users, :some_column end end ``` -------------------------------- ### Add Foreign Key Without Validation (MySQL/MariaDB) Source: https://github.com/ankane/strong_migrations/blob/master/README.md Add a foreign key without validating existing rows for MySQL and MariaDB. This requires disabling foreign key checks temporarily. Ensure no connection pooler is used. ```ruby class AddForeignKeyOnUsers < ActiveRecord::Migration[8.1] def up safety_assured do begin execute "SET SESSION foreign_key_checks = 0" add_foreign_key :users, :orders ensure execute "SET SESSION foreign_key_checks = 1" end end end def down remove_foreign_key :users, :orders end end ``` -------------------------------- ### Automatically Remove Invalid Indexes Source: https://github.com/ankane/strong_migrations/blob/master/README.md Enable the automatic removal of invalid indexes when a migration encounters a lock timeout in Postgres. This prevents errors on subsequent migration runs. ```ruby StrongMigrations.remove_invalid_indexes = true ``` -------------------------------- ### Rename Schema Safely Source: https://github.com/ankane/strong_migrations/blob/master/README.md Renaming a schema in use can cause errors. The safe approach involves creating a new schema, writing to both, backfilling, shifting reads, and then dropping the old schema. ```ruby class RenameUsersToCustomers < ActiveRecord::Migration[8.1] def change rename_schema :users, :customers end end ``` -------------------------------- ### Add Check Constraint Without Validation (Postgres) Source: https://github.com/ankane/strong_migrations/blob/master/README.md Add a check constraint to a table without validating existing rows. This prevents blocking reads and writes during the initial constraint addition. Validate in a separate migration. ```ruby class AddCheckConstraint < ActiveRecord::Migration[8.1] def change add_check_constraint :users, "price > 0", name: "price_check", validate: false end end ``` ```ruby class ValidateCheckConstraint < ActiveRecord::Migration[8.1] def change validate_check_constraint :users, name: "price_check" end end ``` -------------------------------- ### Rename Enum Value Safely Source: https://github.com/ankane/strong_migrations/blob/master/README.md To safely rename an enum value, add a new value, update application code to handle both, backfill data, and then remove the old value. Direct renaming can cause errors. ```ruby class RenameDoneToCompleted < ActiveRecord::Migration[8.1] def change rename_enum_value :status, from: "done", to: "completed" end end ``` ```ruby class AddCompletedToStatus < ActiveRecord::Migration[8.1] def up add_enum_value :status, "completed", after: "done" end end ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.