Amokrane Chentir
Amokrane Chentir

Reputation: 30405

How to write conditional migrations in rails?

I am looking for ways to write migrations in rails that can be executed against the database many times without failing.

For instance let say I have this migration:

class AddUrlToProfile < ActiveRecord::Migration
  def self.up
    add_column :profile, :url, :string
  end

  def self.down
    remove_column :profile, :url
  end
end

If the url column already exists in the Profile table (if the schema.rb has been modified unexpectedly for instance), my migration will fail saying that it's a duplicate!

So how to execute this migration only if it has to?

Thanks

Upvotes: 21

Views: 14133

Answers (4)

not_underground
not_underground

Reputation: 71

Wrapping my migration in a conditional worked for me. Rails 4.X

class AddUrlToProfile < ActiveRecord::Migration
  unless Profile.column_names.include?("url")
    def self.up
      add_column :profile, :url, :string
    end

    def self.down
      remove_column :profile, :url
    end
  end
end 

Upvotes: 3

Jellicle
Jellicle

Reputation: 30286

For Rails 3.X, there's the column_exists?(:table_name, :column_name) method.

For Rails 2.X, you can check the existence of columns with the following:

columns("<table name>").index {|col| col.name == "<column name>"}

...or if you're not in a migration file:

ActiveRecord::Base.connection.columns("<table name>").index {|col| col.name == "<column name>"}

If it returns nil, no such column exists. If it returns a Fixnum, then the column does exist. Naturally, you can put more selective parameters between the {...} if you want to identify a column by more than just its name, for example:

{ |col| col.name == "foo" and col.sql_type == "tinyint(1)" and col.primary == nil }

Upvotes: 15

Fernando Diaz Garrido
Fernando Diaz Garrido

Reputation: 3995

This should work

def self.table_exists?(name)
  ActiveRecord::Base.connection.tables.include?(name)
end

if table_exists?(:profile) && !Profile.column_names.include?("url")
  add_column :profile, :url, :string
end

Upvotes: 10

Pan Thomakos
Pan Thomakos

Reputation: 34350

You can do something like this:

class AddUrlToProfile < ActiveRecord::Migration
  def self.up
    Profile.reset_column_information
    add_column(:profile, :url, :string) unless Profile.column_names.include?('url')

  end

  def self.down
    Profile.reset_column_information
    remove_column(:profile, :url) if Profile.column_names.include?('url')
  end
end

This will reset the column information before it begins - making sure that the Profile model has the up-to-date column information from the actual table. It will then only add the column if it doesn't exist. The same thing happens for the down function, but it only removes the column if it exists.

If you have multiple use cases for this you could factor the code out into a function and re-use that in your migrations.

Upvotes: 51

Related Questions