Optionally creating or removing a column or table with Rails migrations

Here's how to go about creating or removing a column optionally using Rails migrations

A feature that Rails 6.1 added was the ability to use :if_not_exists and :if_exists. This allow us to generate or remove a column or table if it was or wasn’t already present in the database.

It may seem strange that you would want to use methods like this. Surely a glance at your schema.rb file would tell you what tables and columns you have in your database? While this is true sometimes your database can get in a state where the migration tables are out of sync because you’ve been switching branches frequently and are out of sync with other developers on your team. You may also be in the situation where you have had to restore from a database backup and it mightn’t be in sync with what you’re working with now.

Using :if_not_exists

This will create the Articles table if it didn’t already exist in your system:

class CreateArticles < ActiveRecord::Migration[6.1]
  def change
    create_table :articles, if_not_exists: true do |t|
      t.string :title
      t.text :body

      t.timestamps
    end
  end
end

This will create the featured column on the Discounts table if it didn’t already exist:

class AddFeaturedToDiscounts < ActiveRecord::Migration[6.1]
  def change
    add_column :discounts, :featured, :boolean, default: false, if_not_exists: true
  end
end

Using :if_exists

Conversely you can use :if_exists when removing a column (or a table):

class RemoveSubtitleFromArticles < ActiveRecord::Migration[6.1]
  def change
    remove_column :articles, :subtitle, :text, if_exists: true
  end
end 

Before Rails 6.1

We would’ve had to use column_exists? like so:

class RemoveSubtitleFromArticles < ActiveRecord::Migration[5.1]
  def change
    if ActiveRecord::Base.connection.column_exists?(:articles, :subtitle)
      remove_column :articles, :subtitle
    end
  end
end

You can see how this is not as neat as the example given above.

Or to check for a table existing:

class DropArticles < ActiveRecord::Migration[5.1]
  def change
    if ActiveRecord::Base.connection.table_exists?(:articles)
      drop_table :articles do |t|
        t.string :title
        t.text :body

        t.timestamps
      end
    end
  end
end

This article is a part of the "Rails migrations" series


Recent posts View all

Ruby

Forcing a Rails database column to be not null

How you can force a table column to always have something in it with Rails

Writing Marketing

We've deleted an article's worth of unhelpful words

We've improved several pages across our site by removing words that add no value, and often detract from the article.