How to create a migration to remove an index only if it exists, rather than throwing an exception if it doesn't?

Right now, the current migration might fail, if the books table doesn't have created_at or updated_at fields:

class AddTimestampIndexes < ActiveRecord::Migration
  def up
    remove_index :books, :created_at
    remove_index :books, :updated_at

    add_index  :books, :created_at
    add_index  :books, :updated_at
  end

  def down
    remove_index :books, :created_at
    remove_index :books, :updated_at
  end
end

Does remove_index take any options to silently proceed if it fails to remove the index rather than raising an error?


You can use the index_exists? method within your migration to test whether the index you need to remove is actually there.

Take a look at the documentation here: http://apidock.com/rails/ActiveRecord/ConnectionAdapters/SchemaStatements/index_exists%3F

I've not tested it, but you should be able to use something like this:

class AddTimestampIndexes < ActiveRecord::Migration
  def up
    remove_index :books, :created_at if index_exists?(:books, :created_at)
    remove_index :books, :updated_at if index_exists?(:books, :updated_at)

    add_index  :books, :created_at
    add_index  :books, :updated_at
  end

  def down
    remove_index :books, :created_at
    remove_index :books, :updated_at
  end
end

Although, by the looks of things, you really only want to create them if they don't exist? This might be more appropriate for your migration:

class AddTimestampIndexes < ActiveRecord::Migration
  def up
    add_index  :books, :created_at unless index_exists?(:books, :created_at)
    add_index  :books, :updated_at unless index_exists?(:books, :updated_at)
  end

  def down
    remove_index :books, :created_at
    remove_index :books, :updated_at
  end
end

There is also index_name_exists?(table_name, index_name) method which let's you check for an index by it's name. It's helpful for checking for existence of multi-column indexes.

Documentation - index_name_exists


Rails 6.1+ if_exists / if_not_exists options

Rails 6.1 added if_exists option to remove_index in order to not raise an error when the index is already removed.

Rails 6.1 added if_not_exists option to add_index in order to not raise an error when the index is already added.

As a result, your migration can be rewritten in the following way:

class AddTimestampIndexes < ActiveRecord::Migration
  def up
    remove_index :books, :created_at, if_exists: true
    remove_index :books, :updated_at, if_exists: true

    add_index :books, :created_at
    add_index :books, :updated_at
  end

  def down
    remove_index :books, :created_at, if_exists: true
    remove_index :books, :updated_at, if_exists: true
  end
end

Here is a list of the links to the corresponding PRs:

  • Add if_exists option to remove_index,
  • Fix index options for if_not_exists/if_exists.