Active Record Migrations
Migrations are a feature of Active Record that allows you to evolve yourdatabase schema over time. Rather than write schema modifications in pure SQL,migrations allow you to use a Ruby Domain Specific Language (DSL) to describechanges to your tables.
After reading this guide, you will know:
- Which generators you can use to create migrations.
- Which methods Active Record provides to manipulate your database.
- How to change existing migrations and update your schema.
- How migrations relate to
schema.rb. - How to maintain referential integrity.
1. Migration Overview
Migrations are a convenient way toevolve your database schema overtime in a reproducible way.They use a RubyDSL sothat you don't have to writeSQL by hand,allowing your schema and changes to be database independent. We recommend thatyou read the guides forActive Record Basics andtheActive Record Associations to learn more aboutsome of the concepts mentioned here.
You can think of each migration as being a new 'version' of the database. Aschema starts off with nothing in it, and each migration modifies it to add orremove tables, columns, or indexes. Active Record knows how to update yourschema along this timeline, bringing it from whatever point it is in the historyto the latest version. Read more abouthow Rails knows which migration in thetimeline to run.
Active Record updates yourdb/schema.rb file to match the up-to-date structureof your database. Here's an example of a migration:
# db/migrate/20240502100843_create_products.rbclassCreateProducts<ActiveRecord::Migration[8.1]defchangecreate_table:productsdo|t|t.string:namet.text:descriptiont.timestampsendendendThis migration adds a table calledproducts with a string column callednameand a text column calleddescription. A primary key column calledid willalso be added implicitly, as it's the default primary key for all Active Recordmodels. Thetimestamps macro adds two columns,created_at andupdated_at.These special columns are automatically managed by Active Record if they exist.
# db/schema.rbActiveRecord::Schema[8.1].define(version:2024_05_02_100843)do# These are extensions that must be enabled in order to support this databaseenable_extension"plpgsql"create_table"products",force: :cascadedo|t|t.string"name"t.text"description"t.datetime"created_at",null:falset.datetime"updated_at",null:falseendendWe define the change that we want to happen moving forward in time. Before thismigration is run, there will be no table. After it is run, the table will exist.Active Record knows how to reverse this migration as well; if we roll thismigration back, it will remove the table. Read more about rolling backmigrations in theRolling Back section.
After defining the change that we want to occur moving forward in time, it'sessential to consider the reversibility of the migration. While Active Recordcan manage the forward progression of the migration, ensuring the creation ofthe table, the concept of reversibility becomes crucial. With reversiblemigrations, not only does the migration create the table when applied, but italso enables smooth rollback functionality. In case of reverting the migrationabove, Active Record intelligently handles the removal of the table, maintainingdatabase consistency throughout the process. See theReversingMigrations section for more details.
2. Generating Migration Files
2.1. Creating a Standalone Migration
Migrations are stored as files in thedb/migrate directory, one for eachmigration class.
The name of the file is of the formYYYYMMDDHHMMSS_create_products.rb, itcontains a UTC timestamp identifying the migration followed by an underscorefollowed by the name of the migration. The name of the migration class(CamelCased version) should match the latter part of the file name.
For example,20240502100843_create_products.rb should define classCreateProducts and20240502101659_add_details_to_products.rb should defineclassAddDetailsToProducts. Rails uses this timestamp to determine whichmigration should be run and in what order, so if you're copying a migration fromanother application or generating a file yourself, be aware of its position inthe order. You can read more about how the timestamps are used in theRailsMigration Version Control section.
You can override the directory that migrations are stored in by setting themigrations_paths option in yourconfig/database.yml.
When generating a migration, Active Record automatically prepends the currenttimestamp to the file name of the migration. For example, running the commandbelow will create an empty migration file whereby the filename is made up of atimestamp prepended to the underscored name of the migration.
$bin/railsgenerate migration AddPartNumberToProducts# db/migrate/20240502101659_add_part_number_to_products.rbclassAddPartNumberToProducts<ActiveRecord::Migration[8.1]defchangeendendThe generator can do much more than prepend a timestamp to the file name. Basedon naming conventions and additional (optional) arguments it can also startfleshing out the migration.
The following sections will cover the various ways you can create migrationsbased on conventions and additional arguments.
2.2. Creating a New Table
When you want to create a new table in your database, you can use a migrationwith the format "CreateXXX" followed by a list of column names and types. Thiswill generate a migration file that sets up the table with the specifiedcolumns.
$bin/railsgenerate migration CreateProducts name:string part_number:stringgenerates
classCreateProducts<ActiveRecord::Migration[8.1]defchangecreate_table:productsdo|t|t.string:namet.string:part_numbert.timestampsendendendIf you don't specify a type for a field (e.g.,name instead ofname:string), Rails will default to typestring.
The generated file with its contents is just a starting point, and you can addor remove from it as you see fit by editing thedb/migrate/YYYYMMDDHHMMSS_create_products.rb file.
2.3. Adding Columns
When you want to add a new column to an existing table in your database, you canuse a migration with the format "AddColumnToTable" followed by a list of columnnames and types. This will generate a migration file containing the appropriateadd_column statements.
$bin/railsgenerate migration AddPartNumberToProducts part_number:stringThis will generate the following migration:
classAddPartNumberToProducts<ActiveRecord::Migration[8.1]defchangeadd_column:products,:part_number,:stringendendRails infers the target table from the migration name when it matches theadd_<columns>_to_<table> orremove_<columns>_from_<table> patterns. Using aname such asAddPartNumberToProducts lets the generator configureadd_column :products, ... automatically. For more on these conventions, runbin/rails generate migration --help to see the generator usage and examples.
If you'd like to add an index on the new column, you can do that as well.
$bin/railsgenerate migration AddPartNumberToProducts part_number:string:indexThis will generate the appropriateadd_column andadd_indexstatements:
classAddPartNumberToProducts<ActiveRecord::Migration[8.1]defchangeadd_column:products,:part_number,:stringadd_index:products,:part_numberendendYou arenot limited to one magically generated column. For example:
$bin/railsgenerate migration AddDetailsToProducts part_number:string price:decimalThis will generate a schema migration which adds two additional columns to theproducts table.
classAddDetailsToProducts<ActiveRecord::Migration[8.1]defchangeadd_column:products,:part_number,:stringadd_column:products,:price,:decimalendend2.4. Removing Columns
Similarly, if the migration name is of the form "RemoveColumnFromTable" and isfollowed by a list of column names and types then a migration containing theappropriateremove_column statements will be created.
$bin/railsgenerate migration RemovePartNumberFromProducts part_number:stringThis will generate the appropriateremove_column statements:
classRemovePartNumberFromProducts<ActiveRecord::Migration[8.1]defchangeremove_column:products,:part_number,:stringendend2.5. Creating Associations
Active Record associations are used to define relationships between differentmodels in your application, allowing them to interact with each other throughtheir relationships and making it easier to work with related data. To learnmore about associations, you can refer to theAssociation Basicsguide.
One common use case for associations is creating foreign key references betweentables. The generator accepts column types such asreferences to facilitatethis process.References are a shorthand for creating columns,indexes, foreign keys, or even polymorphic association columns.
For example,
$bin/railsgenerate migration AddUserRefToProducts user:referencesgenerates the followingadd_reference call:
classAddUserRefToProducts<ActiveRecord::Migration[8.1]defchangeadd_reference:products,:user,null:false,foreign_key:trueendendThe above migration creates a foreign key calleduser_id in theproductstable, whereuser_id is a reference to theid column in theusers table.It also creates an index for theuser_id column. The schema looks as follows:
create_table"products",force: :cascadedo|t|t.bigint"user_id",null:falset.index["user_id"],name:"index_products_on_user_id"endbelongs_to is an alias ofreferences, so the above could be alternativelywritten as:
$bin/railsgenerate migration AddUserRefToProducts user:belongs_togenerating a migration and schema that is the same as above.
There is also a generator which will produce join tables ifJoinTable is partof the name:
$bin/railsgenerate migration CreateJoinTableUserProduct user productwill produce the following migration:
classCreateJoinTableUserProduct<ActiveRecord::Migration[8.1]defchangecreate_join_table:users,:productsdo|t|# t.index [:user_id, :product_id]# t.index [:product_id, :user_id]endendend2.6. Other Generators that Create Migrations
In addition to themigration generator, themodel,resource, andscaffold generators will create migrations appropriate for adding a new model.This migration will already contain instructions for creating the relevanttable. If you tell Rails what columns you want, then statements for adding thesecolumns will also be created. For example, running:
$bin/railsgenerate model Product name:string description:textThis will create a migration that looks like this:
classCreateProducts<ActiveRecord::Migration[8.1]defchangecreate_table:productsdo|t|t.string:namet.text:descriptiont.timestampsendendendYou can append as many column name/type pairs as you want.
2.7. Passing Modifiers
When generating migrations, you can pass commonly usedtypemodifiers directly on the command line. These modifiers,enclosed by curly braces and following the field type, allow you to tailor thecharacteristics of your database columns without needing to manually edit themigration file afterward.
For instance, running:
$bin/railsgenerate migration AddDetailsToProducts'price:decimal{5,2}' supplier:references{polymorphic}will produce a migration that looks like this
classAddDetailsToProducts<ActiveRecord::Migration[8.1]defchangeadd_column:products,:price,:decimal,precision:5,scale:2add_reference:products,:supplier,polymorphic:trueendendNOT NULL constraints can be imposed from the command line using the!shortcut:
$bin/railsgenerate migration AddEmailToUsers email:string!will produce this migration
classAddEmailToUsers<ActiveRecord::Migration[8.1]defchangeadd_column:users,:email,:string,null:falseendendFor further help with generators, runbin/rails generate --help.Alternatively, you can also runbin/rails generate model --help orbin/railsgenerate migration --help for help with specific generators.
3. Updating Migrations
Once you have created your migration file using one of the generators from theabovesection, you can update the generatedmigration file in thedb/migrate folder to define further changes you want tomake to your database schema.
3.1. Creating a Table
Thecreate_table method is one of the most fundamental migration type, butmost of the time, will be generated for you from using a model, resource, orscaffold generator. A typical use would be
create_table:productsdo|t|t.string:nameendThis method creates aproducts table with a column calledname.
3.1.1. Associations
If you're creating a table for a model that has an association, you can use the:references type to create the appropriate column type. For example:
create_table:productsdo|t|t.references:categoryendThis will create acategory_id column. Alternatively, you can usebelongs_toas an alias forreferences:
create_table:productsdo|t|t.belongs_to:categoryendYou can also specify the column type and index creation using the:polymorphic option:
create_table:taggingsdo|t|t.references:taggable,polymorphic:trueendThis will createtaggable_id,taggable_type columns and the appropriateindexes.
3.1.2. Primary Keys
By default,create_table will implicitly create a primary key calledid foryou. You can change the name of the column with the:primary_key option, likebelow:
classCreateUsers<ActiveRecord::Migration[8.1]defchangecreate_table:users,primary_key:"user_id"do|t|t.string:usernamet.string:emailt.timestampsendendendThis will yield the following schema:
create_table"users",primary_key:"user_id",force: :cascadedo|t|t.string"username"t.string"email"t.datetime"created_at",precision:6,null:falset.datetime"updated_at",precision:6,null:falseendYou can also pass an array to:primary_key for a composite primary key. Readmore aboutcomposite primary keys.
classCreateUsers<ActiveRecord::Migration[8.1]defchangecreate_table:users,primary_key:[:id,:name]do|t|t.string:namet.string:emailt.timestampsendendendIf you don't want a primary key at all, you can pass the optionid: false.
classCreateUsers<ActiveRecord::Migration[8.1]defchangecreate_table:users,id:falsedo|t|t.string:usernamet.string:emailt.timestampsendendend3.1.3. Database Options
If you need to pass database-specific options you can place an SQL fragment inthe:options option. For example:
create_table:products,options:"ENGINE=BLACKHOLE"do|t|t.string:name,null:falseendThis will appendENGINE=BLACKHOLE to the SQL statement used to create thetable.
An index can be created on the columns created within thecreate_table blockby passingindex: true or an options hash to the:index option:
create_table:usersdo|t|t.string:name,index:truet.string:email,index:{unique:true,name:"unique_emails"}end3.1.4. Comments
You can pass the:comment option with any description for the table that willbe stored in the database itself and can be viewed with database administrationtools, such as MySQL Workbench or PgAdmin III. Comments can help team members tobetter understand the data model and to generate documentation in applicationswith large databases. Currently only the MySQL and PostgreSQL adapters supportcomments.
classAddDetailsToProducts<ActiveRecord::Migration[8.1]defchangeadd_column:products,:price,:decimal,precision:8,scale:2,comment:"The price of the product in USD"add_column:products,:stock_quantity,:integer,comment:"The current stock quantity of the product"endend3.2. Creating a Join Table
The migration methodcreate_join_table creates anHABTM (has and belongsto many) jointable. A typical use would be:
create_join_table:products,:categoriesThis migration will create acategories_products table with two columns calledcategory_id andproduct_id.
These columns have the option:null set tofalse by default, meaning thatyoumust provide a value in order to save a record to this table. This canbe overridden by specifying the:column_options option:
create_join_table:products,:categories,column_options:{null:true}By default, the name of the join table comes from the union of the first twoarguments provided to create_join_table, in lexical order. In this case,the table would be namedcategories_products.
The precedence between model names is calculated using the<=>operator forString. This means that if the strings are of different lengths,and the strings are equal when compared up to the shortest length, then thelonger string is considered of higher lexical precedence than the shorter one.For example, one would expect the tables "paper_boxes" and "papers" to generatea join table name of "papers_paper_boxes" because of the length of the name"paper_boxes", but it in fact generates a join table name of"paper_boxes_papers" (because the underscore '_' is lexicographicallylessthan 's' in common encodings).
To customize the name of the table, provide a:table_name option:
create_join_table:products,:categories,table_name: :categorizationThis creates a join table with the namecategorization.
Also,create_join_table accepts a block, which you can use to add indices(which are not created by default) or any additional columns you so choose.
create_join_table:products,:categoriesdo|t|t.index:product_idt.index:category_idend3.3. Changing Tables
If you want to change an existing table in place, there ischange_table.
It is used in a similar fashion tocreate_table but the object yielded insidethe block has access to a number of special functions, for example:
change_table:productsdo|t|t.remove:description,:namet.string:part_numbert.index:part_numbert.rename:upccode,:upc_codeendThis migration will remove thedescription andname columns, create a newstring column calledpart_number and add an index on it. Finally, it renamestheupccode column toupc_code.
3.4. Changing Columns
Similar to theremove_column andadd_column methods we coveredearlier, Rails also provides thechange_columnmigration method.
change_column:products,:part_number,:textThis changes the columnpart_number on products table to be a:text field.
Thechange_column command isirreversible. To ensure your migrationcan be safely reverted, you will need to provide your ownreversiblemigration. See theReversible Migrations section for moredetails.
Besideschange_column, thechange_column_null andchange_column_default methods are used to change a null constraint anddefault values of a column.
change_column_default:products,:approved,from:true,to:falseThis changes the default value of the:approved field from true to false. Thischange will only be applied to future records, any existing records do notchange. Usechange_column_null to change a null constraint.
change_column_null:products,:name,falseThis sets:name field on products to aNOT NULL column. This change appliesto existing records as well, so you need to make sure all existing records havea:name that isNOT NULL.
Setting the null constraint totrue implies that column will accept a nullvalue, otherwise theNOT NULL constraint is applied and a value must be passedin order to persist the record to the database.
You could also write the abovechange_column_default migration aschange_column_default :products, :approved, false, but unlike the previousexample, this would make your migration irreversible.
3.5. Column Modifiers
Column modifiers can be applied when creating or changing a column:
commentAdds a comment for the column.collationSpecifies the collation for astringortextcolumn.defaultAllows to set a default value on the column. Note that if youare using a dynamic value (such as a date), the default will only becalculated the first time (i.e. on the date the migration is applied). UsenilforNULL.limitSets the maximum number of characters for astringcolumn andthe maximum number of bytes fortext/binary/integercolumns.nullAllows or disallowsNULLvalues in the column.precisionSpecifies the precision fordecimal/numeric/datetime/timecolumns.scaleSpecifies the scale for thedecimalandnumericcolumns,representing the number of digits after the decimal point.
Foradd_column orchange_column there is no option for adding indexes.They need to be added separately usingadd_index.
Some adapters may support additional options; see the adapter specific API docsfor further information.
default cannot be specified via command line when generating migrations.
3.6. References
Theadd_reference method allows the creation of an appropriately named columnacting as the connection between one or more associations.
add_reference:users,:roleThis migration will create a foreign key column calledrole_id in the userstable.role_id is a reference to theid column in theroles table. Inaddition, it creates an index for therole_id column, unless it is explicitlytold not to do so with theindex: false option.
See also theActive Record Associations guide to learn more.
The methodadd_belongs_to is an alias ofadd_reference.
add_belongs_to:taggings,:taggable,polymorphic:trueThe polymorphic option will create two columns on the taggings table which canbe used for polymorphic associations:taggable_type andtaggable_id.
See this guide to learn more aboutpolymorphic associations.
A foreign key can be created with theforeign_key option.
add_reference:users,:role,foreign_key:trueFor moreadd_reference options, visit theAPIdocumentation.
References can also be removed:
remove_reference:products,:user,foreign_key:true,index:false3.7. Foreign Keys
While it's not required, you might want to add foreign key constraints toguarantee referential integrity.
add_foreign_key:articles,:authorsTheadd_foreign_key call adds a new constraint to thearticles table.The constraint guarantees that a row in theauthors table exists where theid column matches thearticles.author_id to ensure all reviewers listed inthe articles table are valid authors listed in the authors table.
When usingreferences in a migration, you are creating a new column inthe table and you'll have the option to add a foreign key usingforeign_key:true to that column. However, if you want to add a foreign key to an existingcolumn, you can useadd_foreign_key.
If the column name of the table to which we're adding the foreign key cannot bederived from the table with the referenced primary key then you can use the:column option to specify the column name. Additionally, you can use the:primary_key option if the referenced primary key is not:id.
For example, to add a foreign key onarticles.reviewer referencingauthors.email:
add_foreign_key:articles,:authors,column: :reviewer,primary_key: :emailThis will add a constraint to thearticles table that guarantees a row in theauthors table exists where theemail column matches thearticles.reviewerfield.
Several other options such asname,on_delete,if_not_exists,validate,anddeferrable are supported byadd_foreign_key.
Foreign keys can also be removed usingremove_foreign_key:
# let Active Record figure out the column nameremove_foreign_key:accounts,:branches# remove foreign key for a specific columnremove_foreign_key:accounts,column: :owner_idActive Record only supports single column foreign keys.execute andstructure.sql are required to use composite foreign keys. SeeSchema Dumpingand You.
3.8. Composite Primary Keys
Sometimes a single column's value isn't enough to uniquely identify every row ofa table, but a combination of two or more columnsdoes uniquely identify it.This can be the case when using a legacy database schema without a singleidcolumn as a primary key, or when altering schemas for sharding or multitenancy.
You can create a table with a composite primary key by passing the:primary_key option tocreate_table with an array value:
classCreateProducts<ActiveRecord::Migration[8.1]defchangecreate_table:products,primary_key:[:customer_id,:product_sku]do|t|t.integer:customer_idt.string:product_skut.text:descriptionendendendTables with composite primary keys require passing array values ratherthan integer IDs to many methods. See also theActive Record Composite PrimaryKeys guide to learn more.
3.9. Execute SQL
If the helpers provided by Active Record aren't enough, you can use theexecute method to execute SQL commands. For example,
classUpdateProductPrices<ActiveRecord::Migration[8.1]defupexecute"UPDATE products SET price = 'free'"enddefdownexecute"UPDATE products SET price = 'original_price' WHERE price = 'free';"endendIn this example, we're updating theprice column of the products table to'free' for all records.
Modifying data directly in migrations should be approached withcaution. Consider if this is the best approach for your use case, and be awareof potential drawbacks such as increased complexity and maintenance overhead,risks to data integrity and database portability. See theData Migrationsdocumentation for more details.
For more details and examples of individual methods, check the APIdocumentation.
In particular the documentation forActiveRecord::ConnectionAdapters::SchemaStatements, which provides themethods available in thechange,up anddown methods.
For methods available regarding the object yielded bycreate_table, seeActiveRecord::ConnectionAdapters::TableDefinition.
And for the object yielded bychange_table, seeActiveRecord::ConnectionAdapters::Table.
3.10. Using thechange Method
Thechange method is the primary way of writing migrations. It works for themajority of cases in which Active Record knows how to reverse a migration'sactions automatically. Below are some of the actions thatchange supports:
add_check_constraintadd_columnadd_foreign_keyadd_indexadd_referenceadd_timestampschange_column_comment(must supply:fromand:tooptions)change_column_default(must supply:fromand:tooptions)change_column_nullchange_table_comment(must supply:fromand:tooptions)create_join_tablecreate_tabledisable_extensiondrop_join_tabledrop_table(must supply table creation options and block)enable_extensionremove_check_constraint(must supply original constraint expression)remove_column(must supply original type and column options)remove_columns(must supply original type and column options)remove_foreign_key(must supply other table and original options)remove_index(must supply columns and original options)remove_reference(must supply original options)remove_timestamps(must supply original options)rename_columnrename_indexrename_table
change_table is also reversible, as long as the block only callsreversible operations like the ones listed above.
If you need to use any other methods, you should usereversible or write theup anddown methods instead of using thechange method.
3.11. Usingreversible
If you'd like for a migration to do something that Active Record doesn't knowhow to reverse, then you can usereversible to specify what to do when runninga migration and what else to do when reverting it.
classChangeProductsPrice<ActiveRecord::Migration[8.1]defchangereversibledo|direction|change_table:productsdo|t|direction.up{t.change:price,:string}direction.down{t.change:price,:integer}endendendendThis migration will change the type of theprice column to a string, or backto an integer when the migration is reverted. Notice the block being passed todirection.up anddirection.down respectively.
Alternatively, you can useup anddown instead ofchange:
classChangeProductsPrice<ActiveRecord::Migration[8.1]defupchange_table:productsdo|t|t.change:price,:stringendenddefdownchange_table:productsdo|t|t.change:price,:integerendendendAdditionally,reversible is useful when executing raw SQL queries orperforming database operations that do not have a direct equivalent inActiveRecord methods. You can usereversible to specify what to do whenrunning a migration and what else to do when reverting it. For example:
classExampleMigration<ActiveRecord::Migration[8.1]defchangecreate_table:distributorsdo|t|t.string:zipcodeendreversibledo|direction|direction.updo# create a distributors viewexecute<<-SQL CREATE VIEW distributors_view AS SELECT id, zipcode FROM distributors; SQLenddirection.downdoexecute<<-SQL DROP VIEW distributors_view; SQLendendadd_column:users,:address,:stringendendUsingreversible will ensure that the instructions are executed in the rightorder too. If the previous example migration is reverted, thedown block willbe run after theusers.address column is removed and before thedistributorstable is dropped.
3.12. Using theup/down Methods
You can also use the old style of migration usingup anddown methodsinstead of thechange method.
Theup method should describe the transformation you'd like to make to yourschema, and thedown method of your migration should revert thetransformations done by theup method. In other words, the database schemashould be unchanged if you do anup followed by adown.
For example, if you create a table in theup method, you should drop it in thedown method. It is wise to perform the transformations in precisely thereverse order they were made in theup method. The example in thereversiblesection is equivalent to:
classExampleMigration<ActiveRecord::Migration[8.1]defupcreate_table:distributorsdo|t|t.string:zipcodeend# create a distributors viewexecute<<-SQL CREATE VIEW distributors_view AS SELECT id, zipcode FROM distributors; SQLadd_column:users,:address,:stringenddefdownremove_column:users,:addressexecute<<-SQL DROP VIEW distributors_view; SQLdrop_table:distributorsendend3.13. Throwing an error to prevent reverts
Sometimes your migration will do something which is just plain irreversible; forexample, it might destroy some data.
In such cases, you can raiseActiveRecord::IrreversibleMigration in yourdown block.
classIrreversibleMigrationExample<ActiveRecord::Migration[8.1]defupdrop_table:example_tableenddefdownraiseActiveRecord::IrreversibleMigration,"This migration cannot be reverted because it destroys data."endendIf someone tries to revert your migration, an error message will be displayedsaying that it can't be done.
3.14. Reverting Previous Migrations
You can use Active Record's ability to rollback migrations using therevert method:
require_relative"20121212123456_example_migration"classFixupExampleMigration<ActiveRecord::Migration[8.1]defchangerevertExampleMigrationcreate_table(:apples)do|t|t.string:varietyendendendTherevert method also accepts a block of instructions to reverse. This couldbe useful to revert selected parts of previous migrations.
For example, let's imagine thatExampleMigration is committed and it is laterdecided that a Distributors view is no longer needed.
classDontUseDistributorsViewMigration<ActiveRecord::Migration[8.1]defchangerevertdo# copy-pasted code from ExampleMigrationcreate_table:distributorsdo|t|t.string:zipcodeendreversibledo|direction|direction.updo# create a distributors viewexecute<<-SQL CREATE VIEW distributors_view AS SELECT id, zipcode FROM distributors; SQLenddirection.downdoexecute<<-SQL DROP VIEW distributors_view; SQLendend# The rest of the migration was okendendendThe same migration could also have been written without usingrevert but thiswould have involved a few more steps:
- Reverse the order of
create_tableandreversible. - Replace
create_tablewithdrop_table. - Finally, replace
upwithdownand vice-versa.
This is all taken care of byrevert.
4. Running Migrations
Rails provides a set of commands to run certain sets of migrations.
The very first migration related rails command you will use will probably bebin/rails db:migrate. In its most basic form it just runs thechange orupmethod for all the migrations that have not yet been run. If there are no suchmigrations, it exits. It will run these migrations in order based on the date ofthe migration.
Note that running thedb:migrate command also invokes thedb:schema:dumpcommand, which will update yourdb/schema.rb file to match the structure ofyour database.
If you specify a target version, Active Record will run the required migrations(change, up, down) until it has reached the specified version. The version isthe numerical prefix on the migration's filename. For example, to migrate toversion 20240428000000 run:
$bin/railsdb:migrateVERSION=20240428000000If version 20240428000000 is greater than the current version (i.e., it ismigrating upwards), this will run thechange (orup) method on allmigrations up to and including 20240428000000, and will not execute any latermigrations. If migrating downwards, this will run thedown method on all themigrations down to, but not including, 20240428000000.
4.1. Rolling Back
A common task is to rollback the last migration. For example, if you made amistake in it and wish to correct it. Rather than tracking down the versionnumber associated with the previous migration you can run:
$bin/railsdb:rollbackThis will rollback the latest migration, either by reverting thechange methodor by running thedown method. If you need to undo several migrations you canprovide aSTEP parameter:
$bin/railsdb:rollbackSTEP=3The last 3 migrations will be reverted.
In some cases where you modify a local migration and would like to rollback thatspecific migration before migrating back up again, you can use thedb:migrate:redo command. As with thedb:rollback command, you can use theSTEP parameter if you need to go more than one version back, for example:
$bin/railsdb:migrate:redoSTEP=3You could get the same result usingdb:migrate. However, these are therefor convenience so that you do not need to explicitly specify the version tomigrate to.
4.1.1. Transactions
In databases that support DDL transactions, changing the schema in a singletransaction, each migration is wrapped in a transaction.
A transaction ensures that if a migration fails partway through, anychanges that were successfully applied are rolled back, maintaining databaseconsistency. This means that either all operations within the transaction areexecuted successfully, or none of them are, preventing the database from beingleft in an inconsistent state if an error occurs during the transaction.
If the database does not support DDL transactions with statements that changethe schema, then when a migration fails, the parts of it that have succeededwill not be rolled back. You will have to rollback the changes manually.
There are queries that you can’t execute inside a transaction though, and forthese situations you can turn the automatic transactions off withdisable_ddl_transaction!:
classChangeEnum<ActiveRecord::Migration[8.1]disable_ddl_transaction!defupexecute"ALTER TYPE model_size ADD VALUE 'new_value'"endendRemember that you can still open your own transactions, even if you are ina Migration with self.disable_ddl_transaction!.
4.2. Setting Up the Database
Thebin/rails db:setup command will create the database, load the schema, andinitialize it with the seed data.
4.3. Preparing the Database
Thebin/rails db:prepare command is similar tobin/rails db:setup, but itoperates idempotently, so it can safely be called several times, but it willonly perform the necessary tasks once.
- If the database has not been created yet, the command will run as the
bin/rails db:setupdoes. - If the database exists but the tables have not been created, the command willload the schema, run any pending migrations, dump the updated schema, andfinally load the seed data. See theSeeding Datadocumentation for more details.
- If the database and tables exist, the command will do nothing.
Once the database and tables exist, thedb:prepare task will not try to reloadthe seed data, even if the previously loaded seed data or the existing seed filehave been altered or deleted. To reload the seed data, you can manually runbin/rails db:seed:replant.
This task will only load seeds if one of the databases or tables createdis a primary database for the environment or is configured withseeds: true.
4.4. Resetting the Database
Thebin/rails db:reset command will drop the database and set it up again.This is functionally equivalent tobin/rails db:drop db:setup.
This is not the same as running all the migrations. It will only use thecontents of the currentdb/schema.rb ordb/structure.sql file. If amigration can't be rolled back,bin/rails db:reset may not help you. To findout more about dumping the schema seeSchema Dumping and You section.
If you need an alternative todb:reset that explicitly runs all migrations,consider using thebin/rails db:migrate:reset command. You can follow thatcommand withbin/rails db:seed if needed.
bin/rails db:reset rebuilds the database using the current schema. Onthe other hand,bin/rails db:migrate:reset replays all migrations from thebeginning, which can lead to schema drift if, for example, migrations have beenaltered, reordered, or removed.
4.5. Running Specific Migrations
If you need to run a specific migration up or down, thedb:migrate:up anddb:migrate:down commands will do that. Just specify the appropriate versionand the corresponding migration will have itschange,up ordown methodinvoked, for example:
$bin/railsdb:migrate:upVERSION=20240428000000By running this command thechange method (or theup method) will beexecuted for the migration with the version "20240428000000".
First, this command will check whether the migration exists and if it hasalready been performed and if so, it will do nothing.
If the version specified does not exist, Rails will throw an exception.
$bin/railsdb:migrateVERSION=00000000000000rails aborted!ActiveRecord::UnknownMigrationVersionError:No migration with version number 00000000000000.4.6. Running Migrations in Different Environments
By default runningbin/rails db:migrate will run in thedevelopmentenvironment.
To run migrations against another environment you can specify it using theRAILS_ENV environment variable while running the command. For example to runmigrations against thetest environment you could run:
$bin/railsdb:migrateRAILS_ENV=test4.7. Changing the Output of Running Migrations
By default migrations tell you exactly what they're doing and how long it took.A migration creating a table and adding an index might produce output like this
== CreateProducts: migrating =================================================-- create_table(:products) -> 0.0028s== CreateProducts: migrated (0.0028s) ========================================Several methods are provided in migrations that allow you to control all this:
| Method | Purpose |
|---|---|
suppress_messages | Takes a block as an argument and suppresses any output generated by the block. |
say | Takes a message argument and outputs it as is. A second boolean argument can be passed to specify whether to indent or not. |
say_with_time | Outputs text along with how long it took to run its block. If the block returns an integer it assumes it is the number of rows affected. |
For example, take the following migration:
classCreateProducts<ActiveRecord::Migration[8.1]defchangesuppress_messagesdocreate_table:productsdo|t|t.string:namet.text:descriptiont.timestampsendendsay"Created a table"suppress_messages{add_index:products,:name}say"and an index!",truesay_with_time"Waiting for a while"dosleep10250endendendThis will generate the following output:
== CreateProducts: migrating =================================================-- Created a table -> and an index!-- Waiting for a while -> 10.0013s -> 250 rows== CreateProducts: migrated (10.0054s) =======================================If you want Active Record to not output anything, then runningbin/railsdb:migrate VERBOSE=false will suppress all output.
4.8. Rails Migration Version Control
Rails keeps track of which migrations have been run through theschema_migrations table in the database. When you run a migration, Railsinserts a row into theschema_migrations table with the version number of themigration, stored in theversion column. This allows Rails to determine whichmigrations have already been applied to the database.
For example, if you have a migration file named 20240428000000_create_users.rb,Rails will extract the version number (20240428000000) from the filename andinsert it into the schema_migrations table after the migration has beensuccessfully executed.
You can view the contents of the schema_migrations table directly in yourdatabase management tool or by using Rails console:
rails dbconsoleThen, within the database console, you can query the schema_migrations table:
SELECT*FROMschema_migrations;This will show you a list of all migration version numbers that have beenapplied to the database. Rails uses this information to determine whichmigrations need to be run when you run rails db:migrate or rails db:migrate:upcommands.
5. Changing Existing Migrations
Occasionally you will make a mistake when writing a migration. If you havealready run the migration, then you cannot just edit the migration and run themigration again: Rails thinks it has already run the migration and so will donothing when you runbin/rails db:migrate. You must rollback the migration(for example withbin/rails db:rollback), edit your migration, and then runbin/rails db:migrate to run the corrected version.
In general, editing existing migrations that have been already committed tosource control is not a good idea. You will be creating extra work for yourselfand your co-workers and cause major headaches if the existing version of themigration has already been run on production machines. Instead, you should writea new migration that performs the changes you require.
However, editing a freshly generated migration that has not yet been committedto source control (or, more generally, has not been propagated beyond yourdevelopment machine) is common.
Therevert method can be helpful when writing a new migration to undo previousmigrations in whole or in part (seeReverting Previous Migrations above).
6. Schema Dumping and You
6.1. What are Schema Files for?
Migrations, mighty as they may be, are not the authoritative source for yourdatabase schema.Your database remains the source of truth.
By default, Rails generatesdb/schema.rb which attempts to capture the currentstate of your database schema.
It tends to be faster and less error prone to create a new instance of yourapplication's database by loading the schema file viabin/rails db:schema:loadthan it is to replay the entire migration history.Old migrations may failto apply correctly if those migrations use changing external dependencies orrely on application code which evolves separately from your migrations.
Schema files are also useful if you want a quick look at what attributes anActive Record object has. This information is not in the model's code and isfrequently spread across several migrations, but the information is nicelysummed up in the schema file.
6.2. Types of Schema Dumps
The format of the schema dump generated by Rails is controlled by theconfig.active_record.schema_format setting defined inconfig/application.rb, or theschema_format value in the database configuration.By default, the format is:ruby, or alternatively can be set to:sql.
6.2.1. Using the default:ruby schema
When:ruby is selected, then the schema is stored indb/schema.rb. If youlook at this file you'll find that it looks an awful lot like one very bigmigration:
ActiveRecord::Schema[8.1].define(version:2008_09_06_171750)docreate_table"authors",force:truedo|t|t.string"name"t.datetime"created_at"t.datetime"updated_at"endcreate_table"products",force:truedo|t|t.string"name"t.text"description"t.datetime"created_at"t.datetime"updated_at"t.string"part_number"endendIn many ways this is exactly what it is. This file is created by inspecting thedatabase and expressing its structure usingcreate_table,add_index, and soon.
6.2.2. Using the:sql schema dumper
However,db/schema.rb cannot express everything your database may support suchas triggers, sequences, stored procedures, etc.
While migrations may useexecute to create database constructs that are notsupported by the Ruby migration DSL, these constructs may not be able to bereconstituted by the schema dumper.
If you are using features like these, you should set the schema format to:sqlin order to get an accurate schema file that is useful to create new databaseinstances.
When the schema format is set to:sql, the database structure will be dumpedusing a tool specific to the database intodb/structure.sql. For example, forPostgreSQL, thepg_dump utility is used. For MySQL and MariaDB, this file willcontain the output ofSHOW CREATE TABLE for the various tables.
To load the schema fromdb/structure.sql, runbin/rails db:schema:load.Loading this file is done by executing the SQL statements it contains. Bydefinition, this will create a perfect copy of the database's structure.
6.3. Schema Dumps and Source Control
Because schema files are commonly used to create new databases, it is stronglyrecommended that you check your schema file into source control.
Merge conflicts can occur in your schema file when two branches modify schema.To resolve these conflicts runbin/rails db:migrate to regenerate the schemafile.
Newly generated Rails apps will already have the migrations folderincluded in the git tree, so all you have to do is be sure to add any newmigrations you add and commit them.
7. Active Record and Referential Integrity
The Active Record pattern suggests that intelligence should primarily reside inyour models rather than in the database. Consequently, features like triggers orconstraints, which delegate some of that intelligence back into the database,are not always favored.
Validations such asvalidates :foreign_key, uniqueness: true are one way inwhich models can enforce data integrity. The:dependent option on associationsallows models to automatically destroy child objects when the parent isdestroyed. Like anything which operates at the application level, these cannotguarantee referential integrity and so some people augment them withforeignkey constraints in the database.
In practice, foreign key constraints and unique indexes are generally consideredsafer when enforced at the database level. Although Active Record does notprovide direct support for working with these database-level features, you canstill use the execute method to run arbitrary SQL commands.
It's worth emphasizing that while the Active Record pattern emphasizes keepingintelligence within models, neglecting to implement foreign keys and uniqueconstraints at the database level can potentially lead to integrity issues.Therefore, it's advisable to complement the AR pattern with database-levelconstraints where appropriate. These constraints should have their counterpartsexplicitly defined in your code using associations and validations to ensuredata integrity across both application and database layers.
8. Migrations and Seed Data
The main purpose of the Rails migration feature is to issue commands that modifythe schema using a consistent process. Migrations can also be used to add ormodify data. This is useful in an existing database that can't be destroyed andrecreated, such as a production database.
classAddInitialProducts<ActiveRecord::Migration[8.1]defup5.timesdo|i|Product.create(name:"Product ##{i}",description:"A product.")endenddefdownProduct.delete_allendendTo add initial data after a database is created, Rails has a built-in 'seeds'feature that speeds up the process. This is especially useful when reloading thedatabase frequently in development and test environments, or when setting upinitial data for production.
To get started with this feature, open updb/seeds.rb and add some Ruby code,then runbin/rails db:seed.
The code here should be idempotent so that it can be executed at any pointin every environment.
["Action","Comedy","Drama","Horror"].eachdo|genre_name|MovieGenre.find_or_create_by!(name:genre_name)endThis is generally a much cleaner way to set up the database of a blankapplication.
9. Old Migrations
Thedb/schema.rb ordb/structure.sql is a snapshot of the current state ofyour database and is the authoritative source for rebuilding that database. Thismakes it possible to delete or prune old migration files.
When you delete migration files in thedb/migrate/ directory, any environmentwherebin/rails db:migrate was run when those files still existed will hold areference to the migration timestamp specific to them inside an internal Railsdatabase table namedschema_migrations. You can read more about this in theRails Migration Version Control section.
If you run thebin/rails db:migrate:status command, which displays the status(up or down) of each migration, you should see********** NO FILE **********displayed next to any deleted migration file which was once executed on aspecific environment but can no longer be found in thedb/migrate/ directory.
9.1. Migrations from Engines
When dealing with migrations fromEngines, there's a caveat to consider.Rake tasks to install migrations from engines are idempotent, meaning they willhave the same result no matter how many times they are called. Migrationspresent in the parent application due to a previous installation are skipped,and missing ones are copied with a new leading timestamp. If you deleted oldengine migrations and ran the install task again, you'd get new files with newtimestamps, anddb:migrate would attempt to run them again.
Thus, you generally want to preserve migrations coming from engines. They have aspecial comment like this:
# This migration comes from blorgh (originally 20210621082949)10. Miscellaneous
10.1. Using UUIDs instead of IDs for Primary Keys
By default, Rails uses auto-incrementing integers as primary keys for databaserecords. However, there are scenarios where using Universally Unique Identifiers(UUIDs) as primary keys can be advantageous, especially in distributed systemsor when integration with external services is necessary. UUIDs provide aglobally unique identifier without relying on a centralized authority forgenerating IDs.
10.1.1. Enabling UUIDs in Rails
Before using UUIDs in your Rails application, you'll need to ensure that yourdatabase supports storing them. Additionally, you may need to configure yourdatabase adapter to work with UUIDs.
If you are using a version of PostgreSQL prior to 13, you may still needto enable the pgcrypto extension to access thegen_random_uuid() function.
Rails Configuration
In your Rails application configuration file (
config/application.rb), addthe following line to configure Rails to generate UUIDs as primary keys bydefault:config.generatorsdo|g|g.orm:active_record,primary_key_type: :uuidendThis setting instructs Rails to use UUIDs as the default primary key typefor ActiveRecord models.
Adding References with UUIDs:
When creating associations between models using references, ensure that youspecify the data type as :uuid to maintain consistency with the primary keytype. For example:
create_table:posts,id: :uuiddo|t|t.references:author,type: :uuid,foreign_key:true# Other columns...t.timestampsendIn this example, the
author_idcolumn in the posts table references theidcolumn of the authors table. By explicitly setting the type to:uuid,you ensure that the foreign key column matches the data type of the primarykey it references. Adjust the syntax accordingly for other associations anddatabases.Migration Changes
When generating migrations for your models, you'll notice that it specifiesthe id to be of type
uuid:$bin/railsg migration CreateAuthorsclassCreateAuthors<ActiveRecord::Migration[8.1]defchangecreate_table:authors,id: :uuiddo|t|t.timestampsendendendwhich results in the following schema:
create_table"authors",id: :uuid,default:->{"gen_random_uuid()"},force: :cascadedo|t|t.datetime"created_at",precision:6,null:falset.datetime"updated_at",precision:6,null:falseendIn this migration, the
idcolumn is defined as a UUID primary key with adefault value generated by thegen_random_uuid()function.
UUIDs are guaranteed to be globally unique across different systems, making themsuitable for distributed architectures. They also simplify integration withexternal systems or APIs by providing a unique identifier that doesn't rely oncentralized ID generation, and unlike auto-incrementing integers, UUIDs don'texpose information about the total number of records in a table, which can bebeneficial for security purposes.
However, UUIDs can also impact performance due to their size and are harder toindex. UUIDs will have worse performance for writes and reads compared withinteger primary keys and foreign keys.
Therefore, it's essential to evaluate the trade-offs and consider thespecific requirements of your application before deciding to use UUIDs asprimary keys.
10.2. Data Migrations
Data migrations involve transforming or moving data within your database. InRails, it is generally not advised to perform data migrations using migrationfiles. Here’s why:
- Separation of Concerns: Schema changes and data changes have differentlifecycles and purposes. Schema changes alter the structure of your database,while data changes alter the content.
- Rollback Complexity: Data migrations can be hard to rollback safely andpredictably.
- Performance: Data migrations can take a long time to run and may lock yourtables, affecting application performance and availability.
Instead, consider using themaintenance_tasks gem. Thisgem provides a framework for creating and managing data migrations and othermaintenance tasks in a way that is safe and easy to manage without interferingwith schema migrations.