The defest of leppards

Matt Jankowski

If you have any moderately old rails applications in production, and you find yourself screwing around with indexes in newer migrations, keep in mind that the index naming convention rails uses changed at some point (specifically, in changeset 4768).

So if you had this in an application from a while ago - specifically, if you have an application that was in production prior to the 1.2 release of rails - and you have a migration like this…

# db/migrate/097_add_title_url_to_articles
class AddTitleUrlToArticles < ActiveRecord::Migration
  def self.up
    add_column :articles, :title_url, :string
    add_index :articles, :title_url
  end
  def self.down
    remove_column :articles, :title_url
  end
end

…and as part of regular maintenance you create a migration like this…

# db/migrate/297_add_indexes_to_articles
class AddIndexesToArticles < ActiveRecord::Migration
  def self.up
    remove_index :articles, :site_id
    remove_index :articles, :title_url
    add_index :articles, [:site_id, :title_url]
  end
  def self.down
    remove_index :articles, [:site_id, :title_url]
    add_index :articles, :title_url
    add_index :articles, :site_id
  end
end

''

…assuming that you have entirely dropped and rebuilt your development environment database at some point since you wrote the original migration (which may be quite some time ago, at this point) - the migrations will run fine, and your local development database will have the indexes you want it to.

However, when it’s time to go to production, and you’re dealing with a database on the live cluster which has not been reset/rebuilt/whatever since it’s initial deployment (because seriously…why would it have?), you will find that migration #297 does not run against the production data because the application will try to use a different name than what it originally used when it was running an older version of rails with the former naming style. Hmmm.

This is not a problem though, rails gives us a :name option on indexes in migrations. You can change your new migration to reference what you know the old indexes were called…

# db/migrate/297_add_indexes_to_articles
class AddIndexesToArticles < ActiveRecord::Migration
  def self.up
    # The indexes below were made with "old style" database index naming
    remove_index :articles, :name => :articles_site_id_index
    remove_index :articles, :name => :articles_title_url_index
    add_index :articles, [:site_id, :title_url]
  end
  def self.down
    remove_index :articles, [:site_id, :title_url]
    # Restore indexes with "old style" naming
    add_index :articles, :title_url, :name => :article_title_url_index
    add_index :articles, :site_id, :name => :article_site_id_index
  end
end

…but wait! You’re not done.

Even though you can deploy now and the production database will run the migrations successfully, what about the new developers coming onto the project who are going to be building a database from scratch? Now we need to go modify the old migration so that it produces an index named the way the indexes were originally named back in the time that they were actually run on the production server, using that earlier version of the application and of rails.

# db/migrate/097_add_title_url_to_articles
class AddTitleUrlToArticles < ActiveRecord::Migration
  def self.up
    add_column :articles, :title_url, :string
    add_index :articles, :title_url, :name => :articles_title_url_index
  end
  def self.down
    remove_column :articles, :title_url
  end
end

This is all sort of annoying, and violates the never commit a change to an old migration rule. However, since the framework doesn’t handle it for you, you get to handle it for yourself.