Rails Migration — Part 3

Juzer Shakir
Jul 28, 2021 · 6 min read

Updating existing tables with different migration methods.

Prerequisite:

This article is a continuation of previous articles:

Topics covered in this article

Standalone Migration

In my previous article, we had 2 migration files that created 2 tablesauthor and book with some attributes, after running the migration. Now let’s modify our table by generating a new migration file:

rails g migration AddColumnsToAuthors 

which will create the following file:

db/migrate/20210725125500_add_columns_to_authors.rb

this will create an empty migration file also called a Standalone Migration file:

But if we gave attribute name and its type to the above migration task, like:

rails g migration AddColumnsToAuthors nationality:string

then it generates the migration file like this:

We see that it implicitly added the add_column method with an attribute name nationality as string type to authors table.

A migration name of this form AddXxxToTable followed by attribute name and type, attribute_name:attribute_type, will invoke add_column method to add an attribute to the appropriate table as mentioned in migration name, Table .

Remember migration name should be in CamelCase and column names in snake_case.

Similarly, if we had the migration name of the form RemoveXxxFromTable followed by attribute name and type, it will invoke remove_column method like:

rails g migration RemoveColumnsFromAuthors nationality:string

which generates a migration file like this:

Now, in total, we have 4 migration files and if we run db:migrate, this would not modify our table in any way as 3rd migration creates an attribute and the 4th migration deletes it, it doesn’t update our schema file in any meaningful way so let's delete the 3rd and 4th migration files. We can delete manually both files with GUI or from CMD we can run the following:

$ rails d migration RemoveColumnsFromAuthorsremove    db/migrate/20210725132852_remove_something_from_authors.rb$ rails d migration AddColumnsToAuthorsremove    db/migrate/20210725131028_add_something_to_authors.rb

We give command d, short of destroy, followed by migration file name.. Works the opposite of command g / generate migration task.

Note : Do not delete migrated files. First rollback those and then delete it.

However, we are not limited to just add_column and remove_column methods.

Migration Methods

change method

Official ruby documentation lists every method available to use under the change method in the migration file. Here’s a snap of it:

However, not all methods are reversible. Here’s a list of reversible methods. In addition to those methods, there are a couple more: change_column_comment & change_table_comment, and for both of these to be reversible we need to pass hash containing :from and :to. For methods that aren’t reversible, use those methods under up & down method or reversible method instead of change method. (elaborated in a later section)

So let's apply some of the migration methods to modify our table in the database.

To do:

In authors table:

  • Change an attribute name name to full_name.
  • Add new attributes: original_language, nationality, genre as string type.
  • Add new attributes: fiction as boolean type and set default as true.
  • Add new attributes: max_est_sales (maximum estimated sales) as an integer type and set its limit as 8.
  • Add new attribute: major_works as text type.

Attribute Modifiers:

These are the modifiers that are available to us when we want to create or change an attribute of a table:

:limit 👉 Sets maximum character limit for a :string attribute-type whereas for :text, :binary, and :integer attribute-types, it sets the maximum number of bytes.

:default 👉 Sets attributes’ default value. Use nil for NULL. (cannot be provided through the command line)

:null 👉 Allows or disallows NULL values in the column. Set false to disallow null value. By default, all attributes null values are set to true. (cannot be provided through the command line).

:precision 👉 Allows the total number of digits which includes before and after a decimal point. For example, if precision is set to 5 for an attribute, it can have values in the following form: 99999, 999.99, 9.9999, etc. We can specify precision for these attribute types: :decimal, :numeric, :datetime and :time.

:scale 👉 Total number of digits that are allowed after a decimal point. We can specify precision for these attribute types: :decimal and :numeric.

:comment 👉 Specifies the comment for the column. Can be specified for the table too.

:if_not_exists 👉 If the column already exists, do not try to re-add it. This will avoid duplicate column errors.

All of these modifiers are great tools for us to store the desired value in tables. And we will use a few of these modifiers,default and limit, to complete the ‘To Do’ task.

Note: there are many other modifiers available for other methods, more on this here.

To complete our ‘To Do’ task, let's give a standalone migration as:

rails g migration AddDetailsToAuthors

Adding fiction and max_est_sales attributes with their modifiers to migration file:

rails db:migrate

Our schema file:

change_table method

Similar to the create_table method we looked at in part 1, change_table is used to update the existing table in our database and it has more methods available. And it too is defined in the scope of the change method in the migration file.

List of methods available to use:

Our ‘To Do’ task is not yet complete as we do have to add some more attributes to our table. Let's create another standalone migration and use the change_table method.

Our migration file:

Instead of using change_table method, we could also use the add_column method as an alternative to achieve the following task but then we would need to explicitly specify the table name :authors each time we gave add_column method. So, the change_table provides a little shortcut by yielding table :authors inside the block of change_table.

This will update our schema file accordingly.

All of the methods of the change_table are reversible except for change, change_default, remove. So if we gave any of these 3 methods inside the change_table method and executed db:rollback (assuming they’re migrated), it would raise an error.

Let's try it out by removing the fiction attribute with the remove method.

After migrating this file, if we reverse this migration file with rails db:rollback, it will give us the following error:

Caused by:
ActiveRecord::IrreversibleMigration:
This migration uses remove_columns, which is not automatically reversible.
To make the migration reversible you can either:
1. Define #up and #down methods in place of the #change method.
2. Use the #reversible method to define reversible behavior.

It invoked the IrreversibleMigration class which tells us how to overcome this error. There are 2 ways, instead of using change method, use up & down or reversible method.

up & down method

Instead of change method, we gave 2 methods, up & down. When running rails db:migrate, it triggers to execute the up or change method for each migration file and in this example it executes the up method which deletes the specified attribute. When running rails db:rollback it executes thedown method which creates the attribute that was deleted.

So in a nutshell, The up method should describe the transformation you'd like to make to your schema, and the down method should revert the transformations done by the up method. In other words, the database schema should be unchanged if you executed up followed by a down. For example, if you create a table in the up method, you should drop it in the down method.

If for some reason migration is irreversible, then we should raise ActiveRecord::IrreversibleMigration in down method, so if someone tries to revert our migration, an error message will be displayed saying that it can't be done.

reversible method

Inside the change method, we give reversible method and giving up and down methods inside its block.

The difference between the up & down and thereversible method is that in reversible, up & down methods are inside the block of reversible which itself is inside the block of change method. It's a syntactical difference rather than logical. You can use any of these 2 methods which suit you.

These methods should be used when Active Record can’t reverse a small part of a migration, for instance when using raw SQL statements.

Well, that’s all the basics we need to know about AR Migrations. It was long 3 articles that covered different aspects of AR Migration, and if you have read all of them, I am humbled and I thank you sincerely.

Geek Culture

Proud to geek out. Follow to join our +1.5M monthly readers.