rails

Rails Migration A Complete Guide

A Rails migration is a device for changing an software’s database schema. As an alternative of managing SQL scripts, you define database modifications in a domain-specific language (DSL). The code is database-independent, so you possibly can easily move your app to a brand new platform. You’ll be able to roll migrations back, and handle them alongside your software supply code.

Let’s check out what Rails migrations are, why you may need them, and stroll via examples using the pattern code we used to point out you the way to troubleshoot Ruby purposes.

What’s a Rails migration?

Requirements change all the time, and people modifications typically lead to database modifications. Migrations offer you a method to modify your database schema inside your Rails software. So you employ Ruby code as an alternative of SQL. Using Rails migrations as an alternative of SQL has a number of benefits.

Rails purposes that can stay inside the Lively Document model are database-independent. If you must write SQL to vary your schema, you lose this independence. Migrations keep away from this because you make the modifications in platform-independent Ruby.

Migrations maintain your database schema modifications together with your software code. They’re written in Ruby and versioned with the rest of your app. Positive, you possibly can (and will) model your SQL information, however do they belong in the same place? Not everybody feels the same approach about that question. However it’s not a problem with Rails.

Migrations are additive. Every one represents a new version of your database schema. Rails purposes can evolve, and publishing a new migration with a new release of your software isn’t uncommon.

Why would I want it?

Rails migrations are helpful any time you’ll want to make a change to your software’s database. For those who’re working with Rails Lively Data, manipulating the database instantly is a nasty concept.

As we’ll see within the instance under, Lively Document makes use of migrations to replace your app’s schema in schema.rb. This file is what Rails uses to deploy your software to a brand new database. So using migrations makes it potential for you to deploy your app to new platforms. You’ll be able to develop on one database and deploy to a different, or deploy to a new database platform in manufacturing.

Migrations are saved as part of your Rails challenge, in order that they’re versioned with the remainder of your code. They’re also straightforward to share throughout improvement teams since each member of the staff can deploy the migration to their native occasion once they replace their tasks.

What can I do with it?

You should use migrations to make any modifications it is advisable to the database(s) your software connects to. You need to use the Rails DSL for these modifications, or you need to use SQL. In fact, in the event you write your personal database code, you lose a lot of the database independence, and sometimes labor-saving, advantages of Rails.

You may also roll migrations again, assuming it didn’t do something irreversible like destroying knowledge.

Let’s check out some particular operations, and then we’ll attempt a couple of hands-on migrations in the sample code.

Add a column

Some of the widespread modifications in an app is including a subject to an present object. So adding a column to a database is a typical migration. In case you’re working with Lively Data, Rails will create the migration for you.

You should use all the Rails primary knowledge varieties with migrations, and it’ll be matched to the corresponding sort within the database you migrate to. Right here’s an inventory of knowledge varieties:

  • string
  • text
  • integer
  • bigint
  • float
  • decimal
  • numeric
  • datetime
  • time
  • date
  • datetime
  • timestamp
  • binary
  • primary_key
  • boolean

You can even specify a database-specific knowledge sort for a column, however this will cause issues should you attempt to migrate your software to a brand new platform.

Change a column

You possibly can change an present column to a brand new identify or knowledge sort as properly. Rails migrations know how you can transfer knowledge to a new sort. Column modifications, nevertheless, aren’t reversible.


Stackify Loves Developers


Add a brand new model (or desk)

Including a brand new class to an software is a frequent change too. Whenever you add a new mannequin to a Rails software, it generates a migration to create the corresponding table for you. If that’s not what you need, you’ll be able to write your personal.

Execute SQL

In the event you want functionality that’s not supported by Lively Document, you possibly can execute SQL inside a migration. It’s as much as you to put in writing the code to undo it, though. We’ll look extra intently at tips on how to implement rollbacks under.

How do I carry out a Rails migration?

Let’s do a couple of migrations on our sample app. However earlier than we start, let’s change over to a MySQL database so we will use SQL to examine the results more shortly than we will with SQLite.

A Rails migration to MySQL

You’ll want a MySQL database to comply with along with this. When you’re on a Mac, Linux, or Home windows 10, the simplest method is probably to spin one up on Docker.

Next, seize the sample code from GitHub. After you’ve pulled a replica, navigate to the config listing and open database.yml.mysql in your favourite editor.

# Mysql
default: &default
adapter: mysql2
username: root
password: password
host: 127.zero.zero.1
improvement:
<<: *default
database: todos_development
check:
<<: *default
database: todos_test
production:
<<: *default
database: todos_prod

This can be a database configuration file that connects to a MySQL database operating on localhost. It’ll use the root consumer and password “password.”Modify this on your database.

Then, copy this file to database.yml in the identical listing, ensuring you save the unique if you want to retain the setup for SQLite.

Next, run Rake to create the database.

rake db:create
Created database ‘todos_development’
Created database ‘todos_test’

You may even see some warnings before Rake creates the tables.

Now, run a migration to arrange the appliance tables.

rake db:migrate
== 20181214203309 CreateTodos: migrating ======================================
— create_table(:todos)
-> zero.0217s
== 20181214203309 CreateTodos: migrated (zero.0218s) =============================

You’ve run your first migration! You moved this software from Sqlite3 to MySQL by changing a configuration file and operating a Rails migration. Rails, by way of the Rake command, deployed the appliance’s schema (however not the info) to a special backend for you.

Start the server, and run a query with HTTPie:

http :3000/todos
HTTP/1.1 200 OK
Cache-Management: max-age=0, personal, must-revalidate
Content material-Sort: software/json; charset=utf-8
ETag: W/”4f53cda18c2baa0c0354bb5f9a3ecbe5″
Transfer-Encoding: chunked
X-Request-Id: 640a17d4-7dad-4bde-a3eb-7c1a7d3c738c
X-Runtime: zero.103446
[]

It is best to see a 200 outcome code for fulfillment and an empty set of todos.

Now, add a todo to the database as we did within the first tutorial:

http POST :3000/todos description=’Exchange doors on limo, ashtrays full’ carried out=zero
HTTP/1.1 201 Created
Cache-Management: max-age=zero, personal, must-revalidate
Content-Sort: software/json; charset=utf-8
ETag: W/”def9359bc9248d983495676ce523c1b6″
Transfer-Encoding: chunked
X-Request-Id: 2485b24a-3f58-44b4-bd82-65c8660ec893
X-Runtime: 0.324976

“created_at”: “2019-04-28T19:07:54.000Z”,
“description”: “Replace doors on limo, ashtrays full”,
“done”: zero,
“id”: 1,
“updated_at”: “2019-04-28T19:07:54.000Z”

You need to see a 201 end result code, and the server echos back the brand new report. You’re able to go!

Add a column

Let’s begin by adding a column to our todos table.

Hook up with the database and get a description of the todos desk:

mysql> desc todos;
+————-+————–+——+—–+———+—————-+
| Area       | Sort         | Null | Key | Default | Additional          |
+————-+————–+——+—–+———+—————-+
| id          | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| description | varchar(255) | YES  |     | NULL    |                |
| carried out        | int(11)      | YES  |     | NULL    |                |
| created_at  | datetime     | NO   |     | NULL    |                |
| updated_at  | datetime     | NO   |     | NULL    |                |
+————-+————–+——+—–+———+—————-+
5 rows in set (zero.00 sec)

We need to add customers to the todo software, so we need to add a username to every report.

First, create a migration:

rails generate migration AddUserToTodos consumer:string
invoke  active_record
create    db/migrate/20190428200056_add_user_to_todos.rb

As the command line implies, this tells Rails to generate a migration for us. It does all of the work for us.

Let’s run this and see what occurs:

rake db:migrate
= 20190428200056 AddUserToTodos: migrating ===============================
— add_column(:todos, :consumer, :string)
-> 0.0234s
== 20190428200056 AddUserToTodos: migrated (0.0235s) ======================

Rake’s output says it created a column. Let’s check out the desk in MySQL now:

mysql> desc todos;
+————-+————–+——+—–+———+—————-+
| Area       | Sort         | Null | Key | Default | Additional          |
+————-+————–+——+—–+———+—————-+
| description | varchar(255) | YES  |     | NULL    |                |
| accomplished        | int(11)      | YES  |     | NULL    |                |
| created_at  | datetime     | NO   |     | NULL    |                |
| updated_at  | datetime     | NO   |     | NULL    |                |
| consumer        | varchar(255) | YES  |     | NULL    |                |
+————-+————–+——+—–+———+—————-+
6 rows in set (zero.01 sec)

There it’s! Rake added the column to the table for us.

Let’s take a better take a look at what Rails did for us before we attempt a number of more examples.

Information

If you created the migration above, the last bit of output was a filename like this: 20190428200056_add_user_to_todos.rb. Discover that file in your challenge’s db/migrate listing and open it in an editor.

class AddUserToTodos < ActiveRecord::Migration[5.2]def change
add_column :todos, :consumer, :string
finish
end

That is the database migration that Rails generated for you. Migrations are Ruby courses that implement change, up, or down strategies. This one implements the change technique. (We’ll take a look at up and down later.) Even with an API reference, it’s straightforward to see what it does: it provides a column named consumer with the info sort string.

Once you passed AddUserToTodos consumer:string to Rails, it recognized the migration identify, AddUserToToDos, as a request to add a column. For those who identify your migration identify “AddXXXToYYY” or “RemoveXXXFromYYY” and move an inventory of column names and types, rails will work out what you’re making an attempt to do. You’re not limited to at least one column both. You possibly can add or remove as many as you want.

Now, do a directory itemizing of the db/migrate listing:

ls -l db/migrate
complete 16
-rw-r–r–  1 egoebelbecker  employees  181 Dec 14 19:05 20181214203309_create_todos.rb
-rw-r–r–  1 egoebelbecker  employees  121 Apr 28 16:00 20190428200056_add_user_to_todos.rb

There’s an older migration already in there. Listed here are the contents:

class CreateTodos < ActiveRecord::Migration[5.2] def change
create_table :todos do |t|
t.string :description
t.integer :carried out
end
finish
end

Rails created a migration once I created the challenge. It’s filename is 20181214203309_create_todos.rb. The timestamp initially of the identify serves as a model ID. That’s the migration Rake used to deploy the appliance to MySQL. Even if you wish to write your migration code by hand, using “rails generate migration” to create the file is a good suggestion since calculating those timestamps may be troublesome.

Rollback a migration

Now, rollback the consumer change:

rake db:rollback
== 20190428200056 AddUserToTodos: reverting ===============================
— remove_column(:todos, :consumer, :string)
-> 0.1666s
== 20190428200056 AddUsernameToTodos: reverted (0.1750s) ======================

If you take a look at the table, the column is gone:

mysql> desc todos;
+————-+————–+——+—–+———+—————-+
| Subject       | Sort         | Null | Key | Default | Additional          |
+————-+————–+——+—–+———+—————-+
| id          | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| description | varchar(255) | YES  |     | NULL    |                |
| executed        | int(11)      | YES  |     | NULL    |                |
| created_at  | datetime     | NO   |     | NULL    |                |
| updated_at  | datetime     | NO   |     | NULL    |                |
+————-+————–+——+—–+———+—————-+
5 rows in set (zero.00 sec)

That was straightforward!

Add it again earlier than shifting on to the subsequent step.

rake db:migrate
= 20190428200056 AddUserToTodos: migrating ===============================
— add_column(:todos, :consumer, :string)
-> zero.0234s
== 20190428200056 AddUserToTodos: migrated (zero.0235s) ======================

Change a column

What happens in case you save a username with each todo and a consumer modifications his or her identify? You’ll want to switch every considered one of their todos! As an alternative of a reputation, let’s save a consumer ID. We’ll change the info sort to an integer.

First, create a migration:

rails generate migration ChangeUserToInt
invoke  active_record
create    db/migrate/20190428213522_change_user_to_int.rb

Next, take a look at the file Rails created:

class ChangeUserToInt < ActiveRecord::Migration[5.2]def change
finish
finish

That is empty migration, so that you’ll have to fill it in. Let’s make a couple of modifications. We need to make the consumer an integer, set a default worth for it, and in addition make it not-nullable.

class ChangeUserToInt < ActiveRecord::Migration[5.2]def change
change_column :todos, :consumer, :integer
change_column_default :todos, :consumer, 999
change_column_null :todos, :consumer, false
end
finish

We’re calling three totally different strategies on this migration. The first modifications the info sort, the second the default value, and the last units the column to non-null.

Now run the migration:

rake db:migrate
== 20190428220000 ChangeUserToInteger: migrating ==============================
— change_column(:todos, :consumer, :integer)
-> zero.0426s
— change_column_default(:todos, :consumer, 999)
-> zero.0192s
— change_column_null(:todos, :consumer, false)
-> 0.0355s
== 20190428220000 ChangeUserToInteger: migrated (0.0975s) =====================

Lastly, take a look at the desk:

mysql> desc todos;
+————-+————–+——+—–+———+—————-+
| Area       | Sort         | Null | Key | Default | Additional          |
+————-+————–+——+—–+———+—————-+
| id          | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| description | varchar(255) | YES  |     | NULL    |                |
| finished        | int(11)      | YES  |     | NULL    |                |
| created_at  | datetime     | NO   |     | NULL    |                |
| updated_at  | datetime     | NO   |     | NULL    |                |
| consumer        | int(11)      | NO   |     | 999     |                |
+————-+————–+——+—–+———+—————-+
6 rows in set (zero.00 sec)

Rake up to date the column’s knowledge sort, default worth, and set it to non-nullable.

Add a brand new table

Now, let’s add the customers table. We’ve already seen a migration that creates a desk. Here’s the one Rails generated back once we created the appliance:

class CreateTodos < ActiveRecord::Migration[5.2]def change
create_table :todos do |t|
t.string :description
t.integer :completed

t.timestamps
end
finish
end

So if you wish to use a migration to create a desk that doesn’t correspond to an Lively Document class, you possibly can write one yourself. The create_table accepts a do loop with an inventory of column definitions.

However the straightforward approach is to generate a brand new model:

rails generate model Customers first_name:string last_name:string e mail:string
invoke  active_record
create    db/migrate/20190428234334_create_users.rb
create    app/models/consumer.rb
invoke    test_unit
create      check/fashions/user_test.rb
create      check/fixtures/customers.yml

Rails prints the file identify of the migration it creates. Take a look at it:

class CreateUsers < ActiveRecord::Migration[5.2]def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :e-mail

t.timestamps
end
finish
end

No surprises there. The last column in the definition, timestamps, is added by Rails by default. This creates the created_at and updated_at columns we noticed earlier in the todos table. What appears to be lacking is the consumer ID though.

Let’s run the migration and see what occurs.

rails db:migrate
== 20190428234334 CreateUsers: migrating ======================================
— create_table(:customers)
-> zero.0212s
== 20190428234334 CreateUsers: migrated (0.0213s) =============================

Now, verify MySQL:

mysql> desc users;
+————+————–+——+—–+———+—————-+
| Area      | Sort         | Null | Key | Default | Additional          |
+————+————–+——+—–+———+—————-+
| first_name | varchar(255) | YES  |     | NULL    |                |
| last_name  | varchar(255) | YES  |     | NULL    |                |
| e-mail      | varchar(255) | YES  |     | NULL    |                |
| created_at | datetime     | NO   |     | NULL    |                |
| updated_at | datetime     | NO   |     | NULL    |                |
+————+————–+——+—–+———+—————-+
6 rows in set (0.01 sec)

Rake added the table and mechanically added a main key to the table named ID. Since this corresponds to the consumer column within the todos table, it is sensible to set that up as a overseas key in todos, doesn’t it?

Including a overseas key

We will add a overseas key to a desk by way of a Rails migration. Generate a new migration:

rails g migration AddForeignKeyToTodos
invoke  active_record
create    db/migrate/20190428235632_add_foreign_key_to_todos.rb

Subsequent, open the brand new file and add the code:

class AddForeignKeyToTodos < ActiveRecord::Migration[5.2]def change
add_foreign_key :todos, :customers
finish
finish

Adding a overseas key appears like the opposite migrations we’ve completed to date. There’s a way that’s named add_foreign_key, simply as you’d anticipate.

Now, run the migration.

rails db:migrate
== 20190428235632 AddForeignKeyToTodos: migrating =============================
— add_foreign_key(:todos, :users)
rails aborted!
StandardError: An error has occurred, all later migrations canceled:

Mysql2::Error: Key column ‘user_id’ doesn’t exist in desk: ALTER TABLE `todos` ADD CONSTRAINT `fk_rails_d94154aa95`
FOREIGN KEY (`user_id`)
REFERENCES `customers` (`id`)

The migration failed! We created an issue for ourselves once we created the consumer column: it must be named user_id if it’s going to act as a overseas key for an ID column.

There are a number of ways to repair this, but let’s think about we’ve got knowledge in the consumer column that we don’t need to lose. We’ll rename the column. We’ll also change the info sort to match the ID column because it must match.

Before we begin though, let’s think about the change we’re going to make. What’s going to happen once we try to change consumer to a overseas key? The conversion will fail because we’ve a worth in that column that doesn’t exist within the customers table. We need to add a consumer to the desk earlier than we make the conversion. We need to execute some SQL.

Executing SQL

First, delete the failed conversion. We’ll add it into the new one.

rm db/migrate/20190428235632_add_foreign_key_to_todos.rb

Now, generate a new one.

rails g migration RenameUserToUserId
invoke  active_record
create    db/migrate/20190429001106_rename_user_to_user_id.rb

That is going to be a sophisticated class. It needs to

  1. rename the column in todos,
  2. change the info sort,
  3. insert a consumer in users,
  4. and, finally, change user_id to a overseas key.

Let’s write the code.

class RenameUserToUserId < ActiveRecord::Migration[5.2]def change
rename_column :todos, :consumer, :user_id
change_column :todos, :user_id, :bigint
execute “insert into users (first_name, last_name, email, id, created_at, updated_at) values(‘Eric’, ‘Goebelbecker’, ‘eric AT ericgoebelbecker.com’, 999, now(), now())”
add_foreign_key :todos, :customers
end
finish

Run this migration, and try the customers table:

mysql> choose * from customers;
+—–+————+————–+—————————+———————+———————+
| id  | first_name | last_name    | e mail                     | created_at          | updated_at          |
+—–+————+————–+—————————+———————+———————+
| 999 | Eric       | Goebelbecker | [email protected] | 2019-04-29 00:34:13 | 2019-04-29 00:34:13 |
+—–+————+————–+—————————+———————+———————+
1 row in set (0.11 sec)

The consumer is there! How does todos look?

mysql> desc todos;
+————-+————–+——+—–+———+—————-+
| Subject       | Sort         | Null | Key | Default | Additional          |
+————-+————–+——+—–+———+—————-+
| id          | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| description | varchar(255) | YES  |     | NULL    |                |
| executed        | int(11)      | YES  |     | NULL    |                |
| created_at  | datetime     | NO   |     | NULL    |                |
| updated_at  | datetime     | NO   |     | NULL    |                |
| user_id     | bigint(20)   | NO   | MUL | 999     |                |
+————-+————–+——+—–+———+—————-+
6 rows in set (zero.00 sec)

It’s all set! We created a relationship between the two tables using a Rails migration.

There’s an issue with this migration, although. Rails doesn’t know the right way to rollback our SQL assertion. You’re solely supposed to put directives in a change technique that Rails knows how you can revert.

Migrations with up and down

Let’s write a migration that may be rolled again. Generate another migration named “AddAnotherUser” and open the file.

class AddAnotherUser < ActiveRecord::Migration[5.2]def up
execute “insert into users (first_name, last_name, email, id, created_at, updated_at) values(‘Eric’, ‘Rekceblebeog’, ‘eric AT unknown.com’, 998, now(), now())”
end
def down
execute “delete from users where id = 998”
finish
end

Rake runs the up technique for a migration and the down technique for a rollback.

Run this migration, and the consumer is added:

rake db:migrate
== 20190429200308 AddAnotherUser: migrating ===================================
— execute(“insert into users (first_name, last_name, email, id, created_at, updated_at) values(‘Eric’, ‘Rekceblebeog’, ‘eric AT unknown.com’, 998, now(), now())”)
-> 0.0271s

== 20190429200308 AddAnotherUser: migrated (0.0273s) ==========================

Roll it again, and the consumer is eliminated:

rake db:rollback
== 20190429200308 AddAnotherUser: reverting ===================================
— execute(“delete from users where id = 998”)
-> 0.0124s
== 20190429200308 AddAnotherUser: reverted (zero.0125s) ==========================


Stackify Loves Developers


How do I verify a Rails migration?

We’ve been checking each of our migrations by logging into MySQL and analyzing the related tables. That is time-consuming and doesn’t scale.

Use Schema.rb to confirm your database schema

I discussed schema.rb at the start of this tutorial. It’s where Rails shops the present state of the appliance’s database schema. While it’s not meant as a option to verify a migration, it’s good for getting an concept of what Rails thinks you needed.

Open up db/schema.rb in an editor:

# This file is auto-generated from the current state of the database. As an alternative
# of modifying this file, please use the migrations function of Lively Document to
# incrementally modify your database after which regenerate this schema definition.
#
# Word that this schema.rb definition is the authoritative source in your
# database schema. If you’ll want to create the appliance database on one other
# system, you need to be utilizing db:schema:load, not operating all the migrations
# from scratch. The latter is a flawed and unsustainable strategy (the extra migrations
# you’ll amass, the slower it’ll run and the larger probability for points).
#
# It’s strongly really helpful that you simply examine this file into your model control system.
ActiveRecord::Schema.outline(version: 2019_04_29_002812) do
create_table “todos”, choices: “ENGINE=InnoDB DEFAULT CHARSET=utf8”, drive: :cascade do |t|
t.string “description”
t.integer “done”
t.datetime “created_at”, null: false
t.datetime “updated_at”, null: false
t.bigint “user_id”, default: 999, null: false
t.index [“user_id”], identify: “fk_rails_d94154aa95”
end
create_table “users”, options: “ENGINE=InnoDB DEFAULT CHARSET=utf8”, pressure: :cascade do |t|
t.string “first_name”
t.string “last_name”
t.string “email”
t.datetime “created_at”, null: false
t.datetime “updated_at”, null: false
finish
add_foreign_key “todos”, “users”
end

This is the present state of the schemas for customers and todos. All the modifications we made are mirrored within the table definitions.

As you’ll be able to see from the feedback, you need to by no means modify this file immediately! All the time use migrations to ensure your database stays in sync together with your code. Verify schema.rb earlier than you deploy to a new setting to ensure your picture of the schema and Rails are in sync.

Rails migration

Use Stackify’s Retrace to watch your app

Stackify Retrace is a strong option to monitor your Rails software. Retrace centralizes your logs so you’ll be able to view them from an internet console and arrange alerts. Retrace also correlates logs with net errors so you possibly can isolate new issues shortly. And if a migration fails, you’ll be able to monitor down the problem in seconds. Sign up for a free trial here.

Migrations are a key Rails benefit

The power to deploy database modifications from code is certainly one of Rails’ strongest options. Rails migrations free you from worrying concerning the differences between numerous SQL grammar so you possibly can focus in your software code. So, take a better take a look at migrations before you write SQL on your Rails app.

Schedule A Demo

About Eric Boersma

This publish was written by Eric Boersma. Eric is a software program developer and improvement supervisor who’s finished all the things from IT security in prescription drugs to writing intelligence software for the US authorities to constructing international improvement teams for non-profits. He loves to talk concerning the things he’s discovered alongside the best way, and he enjoys listening to and studying from others as nicely.