Rails Migrations Tips & Tricks

Rails birds making a migration

Rails comes with a toolkit for managing migrations: the changes you will make to your application’s database as you develop an application. Enabling agility in your process, migrations allow you to continually change the database definition (the schema) as you develop. This can only be possible in the context of an Agile environment, and conversely, Agility can only be possible with the right tools to make changes to your database as you.

That’s why Migrations and Agility go together like peas and carrots. In the alternative styles of software, (that is, Big-Design-Up-Front or “BDUF”) the general mindset of software development is to begin by defining the schema completely up-front and there are no migrations.

With its embrace of change, the Rails-Agile approach lets you make database migrations any time you want. There’s only one caveat: downtime. For most applications, a short period of unavailability in the early mornings or late at night is acceptable. For larger projects, a no-downtime migration strategy becomes necessary. In short, you then need to move towards a two-step deploy process. This beginner article covers only what you need to know about running Rails migrations with standard short downtime periods which is sufficient for most small-to-medium-sized applications.

Reloading After Rails Migrations

Always remember that the Rails migrations are a little “sacred” to Rails. But there isn’t an actual mystery to it: After you change the database, always remember to tell Rails to reload. How you do that differs depending on the version of Rails you are on.

Rails 3.2

User.connection.schema_cache.clear!
User.reset_column_information

(in this example, assume User object related to the table where you made the database modification)

For Rails 4.0, 4.1, 5.0, 5.1, and 5.2, you need to call

User.reset_column_information

The migrations are integrated with the deploy process. This is why you should keep in mind is that the reload happens by each of your webservers. On a 12-Factor platform like Heroku, the answer is often simply to restart the app entirely (which will restart all of the app’s running dynos on Heroku’s grid). If your deploy is more complicated, you can orchestrate the reloading of the column information on each dyno after deploy. (Because that’s a headache, typically you just restart the application which will tell ActiveRecord to reload its cache.)

As of Rails 6, you no longer need to do this.

Defaults In Rails Migrations — Works Even with .new (Rails 5.2+)

Starting with Rails 5.2, defaults apply to both the database and to the objects you create with .new. That’s handy because it means you can just define those defaults in one place.

A Funny Thing About the ActiveRecord::Migration (Rails 5.0 and up)

In the older days of Rails, all migrations were subclasses of ActiveRecord::Migration. Starting in Rails 5, the class name itself is appended with a bracket and the ActiveRecord version (that is, the Rails version) where that migration came from. This is to make sure you don’t try to run a old migration with unsupported syntax on newer versions of Active record, because that API (the old syntax you used) could be deprecated. Migrations created in a Rails 5.0 app look like

ActiveRecord::Migration[5.0]

Many Migration Tricks

Remember you can do more than just field name change in migrations check out

  • change_column
  • add_index
  • remove_index
  • rename_table

Sometimes too much is a good thing.

When you define a :string as a field, for example, the :name field on the fruits table, you can specify to the database what type it will use by specifying the limit: in the migration. This is slightly counter-intuitive because you don’t actually specify tiny, text, mediumtext, or longtext.

What’s interesting here is that automatically pick the smallest necessary type from the text types implemented by your database that would be able to accommodate the value. It rounds up the limits for whichever database you are using. For MySQL, this is:

  • Tiny 256 bytes (string equivalent in Rails)
  • Text 65,535 bytes (text equivalent in Rails if you don’t specify a limit, or your limit < 65535)
  • Medium Text 16,777,215 bytes (If you specify text type and a limit > 65535 but < 16777215)
  • LongText 4,294,967,295 bytes (If you specify text type and a limit > 16777215 up to the LongText maximum)

Remember, when you need to change the storage of your underlying database field, there’s no need to drop and add the field back (loosing the data).

change_column :fruits, :name, :string, limit: 1000000

Raw SQL

Some people like their steaks raw. If you need SQL, you need SQL. Your migrations class implements a handy method called

execute

for just this purpose. For example, you might want to execute to put some fancy indexes on your tables, or adding other views that are non-standard. I also personally really like to have the fields on my database ordered in a logical way. (For example, similar fields grouped together.) If you follow most migration practices, your fields are added in the order they were created by your developers over the lifetime of your application. For this reason, I like to do this after creating a new column

execute("ALTER TABLE fruits MODIFY xyz varchar(20) AFTER some_other_column")

In this example, I would run this just after adding the xyz column to the fruits table. Importantly, you need to know that I would have to specify the type of the column again (even though I just specified it). The only thing this achieves is to move the field (column) xyz to after the column some_other_column, which as I said, keeps your table looking nice and tidy.

I hope you’ve enjoyed this tips & tricks for Rails Migrations.