Rails adds support for if_exists/if_not_exists on remove_column/add_column in migrations


In Rails, when we have a table and we want to add or remove columns from that table, then we write migrations using methods like add_column for adding column and remove_column for removing a column.

Typical add_column and remove_column migrations look like these:

class AddDescriptionToProduct < ActiveRecord::Migration[5.1]
  def change
    add_column :products, :description, :text
  end
end
class RemoveDescriptionFromProduct < ActiveRecord::Migration[5.1]
  def change
    remove_column :products, :description
  end
end

If the column is already present and we try to run add_column migration then we get an error like this:

DuplicateColumn: ERROR:  column "description" of relation "products" already exists

Similarly, if we try to remove a non-existent column using remove_column migration, we get an error like this:

UndefinedColumn: ERROR:  column "description" of relation "products" does not exist

Before Rails 6.1

To handle these errors, we can rewrite above migrations like these:

class AddDescriptionToProduct < ActiveRecord::Migration[5.1]
  def change
    unless ActiveRecord::Base.connection.column_exists?(:products, :description)
      add_column :products, :description, :text
    end
  end
end
class RemoveDescriptionFromProduct < ActiveRecord::Migration[5.1]
  def change
    if ActiveRecord::Base.connection.column_exists?(:products, :description)
      remove_column :products, :description
    end
  end
end

To simplify these conditions, now Rails has added support for if_exists/if_not_exists on remove_column/add_column in migrations.

With Rails 6.1

Adding a column

class AddDescriptionToProduct < ActiveRecord::Migration[6.1]
  def change
    add_column :products, :description, :text, if_not_exists: true
  end
end

If the column doesn’t exist then the above migration will add the column otherwise it won’t raise an error.

-- add_column(:products, :description, :text, {:if_not_exists=>true})
   -> 0.0017s

Removing a column

class RemoveDescriptionFromProduct < ActiveRecord::Migration[6.1]
  def change
    remove_column :products, :description, if_exists: true
  end
end

Similarly, the above migration will remove the column if it exists, else it’ll run the migration without error.

-- remove_column(:products, :description, {:if_exists=>true})
   -> 0.0153s