我正在寻找在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已被意外修改),我的迁移将失败,说它是重复的!
那么只有在必须执行此迁移时才能执行此迁移?
由于
答案 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