2011-02-12 45 views
20

我正在尋找方法來編寫可以在數據庫中多次執行而不失敗的rails中的遷移。如何在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已經例如意外修改),我的遷移將失敗說這是重複的!

那麼如何執行此遷移只有當它有?

感謝

回答

46

你可以做這樣的事情:

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 

開始前這將重置列的信息 - 確保配置文件模型從向上的最新列信息實際表格。然後它將只添加列,如果它不存在。 down函數會發生同樣的情況,但它只會在列存在的情況下刪除列。

如果你有多個用例,你可以將代碼放入一個函數中,並在你的遷移中重用。

+0

很好的解決方案!謝謝! – 2011-02-12 22:20:28

+2

供參考:在遷移過程中顯式引用模型通常是一個壞主意 - 如果將來刪除該模型將會怎樣?還是改變?相反,通過添加class Profile 2015-04-29 20:53:03

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 
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

結束語我的移民在條件爲我工作。 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