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

Amo*_*tir 21 migration ruby-on-rails

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

比如说我有这个迁移:

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

  def self.down
    remove_column :profile, :url
  end
end
Run Code Online (Sandbox Code Playgroud)

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

那么如果必须执行此迁移呢?

谢谢

Pan*_*kos 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
Run Code Online (Sandbox Code Playgroud)

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

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

  • 仅供参考:在您的迁移中明确引用模型通常是一个坏主意 - 如果将来删除该模型该怎么办?还是改变?相反,通过添加```类Profile <ActiveRecord :: Base创建一次性模型; 结束```在迁移类定义的顶部. (2认同)

Jel*_*Cat 15

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

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

columns("<table name>").index {|col| col.name == "<column name>"}
Run Code Online (Sandbox Code Playgroud)

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

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

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

{ |col| col.name == "foo" and col.sql_type == "tinyint(1)" and col.primary == nil }
Run Code Online (Sandbox Code Playgroud)


Fer*_*ido 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
Run Code Online (Sandbox Code Playgroud)