如何在rails中编写条件迁移?

时间:2011-02-12 21:59:57

标签: ruby-on-rails migration

我正在寻找在rails中编写迁移的方法,这些迁移可以多次针对数据库执行而不会失败。

例如,假设我有这种迁移:

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

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

如果url表中已存在Profile列(例如,如果schema.rb已被意外修改),我的迁移将失败,说它是重复的!

那么只有在必须执行此迁移时才能执行此迁移?

由于

4 个答案:

答案 0 :(得分:50)

您可以这样做:

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

这将在列信息开始之前重置它 - 确保Profile模型具有实际表中的最新列信息。然后它只会添加列,如果它不存在。对于down函数也会发生同样的事情,但只有在列存在时才会删除它。

如果您有多个用例,可以将代码分解为函数并在迁移中重复使用。

答案 1 :(得分:15)

对于 Rails 3.X ,有column_exists?(:table_name, :column_name)方法。

对于 Rails 2.X ,您可以使用以下内容检查列的存在:

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

...或者如果您不在迁移文件中:

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

如果返回nil,则不存在此列。如果它返回Fixnum,则该列确实存在。当然,如果您想要通过多个名称来识别列,可以在{...}之间添加更多选择性参数,例如:

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

答案 2 :(得分:8)

这应该有效

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

答案 3 :(得分:3)

在条件下包装我的迁移对我有用。 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