Rails Migration — Part 2
Executing migration files with different migration tasks.
This article is a continuation of the previous article:
Table of Contents
Executing Migration file:
Active Record provides several ways to run migration files, the migrations file we have are:
The very first command we would probably run to create tables in the database is with:
This runs the
up or the
change method for all the migration files that have not yet been run.
db:migrate task will execute these files in ascending order based on the timestamp in their file names.
== 20210720050156 CreateAuthors: migrating ====================================
== 20210720050156 CreateAuthors: migrated (0.0021s) ============================= 20210721053723 CreateBooks: migrating ======================================
== 20210721053723 CreateBooks: migrated (0.0025s) =============================
After running the migration, the following files are created:
- creates table named
db/schema.rbfile (in first migration).
db/development.sqlite3file (in first migration if using SQLite database).
db:test etc which are part of rake library are also supported by rails after its release of version 5. So for Rails application which runs Rails version 5 or later, they can run migration commands with
rails. However, prior to version 5, the command
rake is the only option. More on this here.
db:migrate command invokes the
db:schema:dump task, which will create a
schema.rb file, which is a syntactical representation of our database, in the
db/ directory. More on this is discussed in the Database Schema topic.
It also creates a
development.sqlite3 file in
db/ directory, which we will cover in the next topic.
Each migration is a new ‘version’ of the database.
Assuming you’re using SQLite as a database for building rails applications, the migrations which you will run will create
development.sqlite3 file, which is a visual representation of our database. To view it, you will need to download a software called SQLite browser for Ubuntu.
By default, migrations will run in
development environment. To run the migrations in a different environment we specify
RAILS_ENV variable while running the migration task, like this:
rails db:migrate RAILS_ENV=test
This will run our migration in
Which files migrate first?
As mentioned earlier,
db:migrate task will execute these files in ascending order based on the timestamp in their file names. So in our case, we have the following timestamp in the migration filename:
The authors' migration file was created on 20th July, It will run this migration first followed by the books migration file which was created later on 21st July.
Rails use migrations’ file numbers (the timestamp) to identify them. Active Record won't do anything if we run
db:migrate task on already executed migration files.
The combination of timestamps and recording which migrations have been run, allows Rails to handle common situations that occur with multiple developers.
Before Rails version 2.1, the migration number wasn’t a timestamp but a number that started from
1 and incremented each time a migration file was generated after that. The issue with this approach was that if we had multiple developers working for a project, it was easy to clash with similar migration file names, requiring us to rollback migrations (will be discussed later) and re-number the files manually.
To overcome this, Rails introduced naming migration files with creation time when they were migrated. This made each migration file unique and hence avoided circumstances that developers faced before. However, we can revert to the old numbering scheme by adding the following line to
config.active_record.timestamped_migrations = false
The Active Record database allows us to migrate files in many ways. Following is a list of tasks that update the
db/schema.rb (schema file) to reflect the database.
There will be situations where we need to change/modify the table after we have run our migrations. Editing existing migration files and re-running
db:migrate won’t fix it because rails already know it has executed that migration file, hence
db:migrate won’t do anything.
To change or modify the table, first, we need to give
db:rollback, which is the opposite of
db:migrate, then edit our migration file and then re-run
This will undo our last migration file, by running
change method in the migration file. In our case, it will rollback
20210721053723_create_books.rb file which will delete the
Editing an existing migration file is not a good idea especially if it is already running in a production environment. Instead, we should write a new migration that performs the changes we require to the table as needed.
However, editing a freshly generated migration file that has not yet been migrated is relatively harmless.
database: ..db/development.sqlite3Status Migration ID Migration Name
up 20210720050156 Create authors
down 20210721053723 Create books
This comes in handy when we’re not sure which migration file has been executed. The
up status shows that
db:migrate has migrated that file while the
down status shows it hasn’t.
It helps run a specific migration file whose status is
down. The value passed to
VERSION will be the timestamp of a migration file. In our case, we have
books migration file that we want to run:
rails db:migrate VERSION=20210721053723
== 20210721053723 CreateBooks: migrating ======================================
== 20210721053723 CreateBooks: migrated (0.0040s) =============================
However, in our case, a better shortcut command is
up method for our
books migration file. However, if we wanted to rollback a specific migration file we would give
rails db:migrate:down VERSION=20210721053723
== 20210721053723 CreateBooks: reverting ======================================
== 20210721053723 CreateBooks: reverted (0.0026s) =============================
In our case, it deletes the
For our example, a better alternative shortcut would be
It behaves opposite to the
down task, as discussed above, as creates
book table and updates the schema file:
rails db:migrate:up VERSION=20210721053723
For our example, 2 alternative methods are:
rails db:migrate VERSION=20210721053723
To undo multiple migration files, we provide a
rails db:rollback STEP=2
2 means it will undo the last 2 migration files. In our case, it would delete both tables,
books from database.
1 will undo the last migration file.
For rolling back multiple migrations, the
STEP task provides a more convenient way to migrate instead of providing multiple
redo task is a combo of both
db:migrate in one. And with it, we can give the
STEP task to migrate more than one migration file.
rails db:rollback:redo STEP=2
Loads the seed data into our database through the
db/seeds.rb file. This will execute only after all our migration files have been migrated. This however will not update the schema file as it feeds data to our table and doesn’t change the tables’ metadata.
schema starts with nothing in it, and with each migration, it modifies the metadata of our table or tables. Active Record knows how to update our
schema along this timeline, bringing it from whatever point it is in the history to the latest version. Alongside, Active Record will also update our
db/schema.rb file to match the up-to-date structure of your database.
A look at our
This file is created by inspecting the database and expressing its structure using methods like
change_table, and so on. The
db/schema.rb file attempts to capture the current state of your database schema and is not designed to be edited. It's useful if we want to take a quick look at our database which would show information like how many and which tables are created, how many and what attributes each table has, etc. The information here is nicely summed up for us.
As this file is database-independent, it could be loaded into any database that Active Record supports, such as PostgreSQL, MySQL, etc where it could run against multiple databases.
schema.rb file, the
version key which is passed as an argument to a class method
define, has a value of UTC, the time at which schema file was last updated.
Types of schema dumps:
There are 2 ways to dump schema, either by SQL or Ruby and this value is set in
config.active_record.schema_format setting for which its value can either be
By default, its value is set to
:ruby and the schema is dumped in
db/schema.rb file which we saw above.
However, there are trade-offs using ruby as schema format, as it cannot express database-specific items such as foreign key constraints, triggers, or stored procedures. In a migration file, we can execute these custom SQL statements, however, the schema dumper cannot reconstitute those statements from the database. In such cases, we should set schema format to
:sql is selected then the database structure will be dumped using a tool specific to that database via
db:structure:dump task into
More Migration Tasks
This will delete our database file
db/test.sqlite3 (if it exists) without updating our schema file.
To re-create the database, we can either run
db:migrate which would run all the migration files that would re-create our table but won’t update our schema file version or we can use
It re-creates the database based on the schema file.
This not only creates the database in
development but also in
test environment, as it creates 2 database files in
It re-creates the schema file based on the database.
Assuming if our database is empty or it doesn’t exist, if we run the above task, our schema file would look like this:
ActiveRecord::Schema.define(version: 0) doend
..resets the version to 0.
Current version: 0
It outputs the current version of our schema file.
Normally, after running any migration file with any migration task which updates our schema file, the value of
version will be something like this:
2021_07_23_045128 which captures at what time our schema file was last updated.
Once you have created the tables in a database, how would you modify them? In part 3 I have covered this in-depth. See you there! :)