Rails Migrations
生活随笔
收集整理的這篇文章主要介紹了
Rails Migrations
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
Rails鼓勵敏捷,迭代的開發風格。我們不會第一次就期望得到正確的東西。相反我們會寫測試,并與客戶溝通以加強我們對事物的理解。 要做到這些,我們就需要大量的實踐工作。我們寫測試來幫助規劃我們接口,以便我們能安全地進行修改,我們對應用程序源文件使用版本控制,允許我們回說溯錯誤,并可監視我們每天的改動。 但對于應用程序修改的另一方面來說,我們不能直接使用版本控制來管理。它就是我們在開發過程中,對應用程序中數據庫schema的管理:我們添加一個表,重命名列名稱等等。數據庫的修改需要應用程序的代碼。 很久以來,這一直是個問題。開發者(或數據庫管理員)使用schema進行修改。但是,如果應用程序代碼可以回溯到一個先前版本,但數據庫schema的修改卻不是同步的。因為數據庫本身沒有版本信息。 不久前,開發者以提出了多種解決這個問題的途徑。一個schema保持了數據庫定義語言(DDL),而DDL在版本控制下以源代碼方式定義了schema。無論何時只要你修改了schema,你通過編輯這個文件來反映做出的修改。那么你會中止你的development數據庫,并重新從你寫的DDL中創建schema。如果你需要回溯到一周之前的話,那么你就會按這些步驟來檢查應用程序代碼和DDL:當你重新從DDL創建schema時,你的數據庫會得到及時的回溯。 除了…..因為你每次應用DDL時,你都會中止數據庫,你會丟失放在development數據庫內的數據。如果我們能將數據庫從版本x遷移到y就更好了?不錯,Rails的migration遷移就可讓你做到點。 讓我們先在抽象層上看看migration遷移。假設我們有個存儲定單數據的order表。有一天,我們的客戶要求我們為每個定單上添加用戶的郵件地址。這就包括了對應用程序代碼數據庫shema的修改。要處理它,我們創建了一個數據庫migration遷移,在其內描述“給orders表添加一個e-mail字段”。這個migration遷移放在一個單獨的文件內。我們將其與另外的應用程序文件一起放在版本控制下。然后我們對數據庫使用這個migration遷移,結果列被添加到現有的orders表內。 如何正確地為數據庫完成一個migration遷移呢?每次migration遷移都會有一個序號與其關聯。這些序號從1開始---每個新的migration遷移都會得到下一個有效序號。Rails會記住應用給數據庫migration遷移的最后一個序號。那么你會問,何時應用新的migration遷移來更新schema,它將數據庫schema的序號與有效的migragion遷移序號進行比較。如果它發現migration遷移的序號大于數據庫的schema,它就會應用migration遷移。 但是我們如何回溯一個先前版本的schema呢?我們通過讓每個migration遷移都是可回溯的來做到這一點。每個migration遷移實例上都包含兩個指令集。一套告訴Rails在應用migration遷移時對數據庫做出什么修改,另一套則告訴Rails如何回溯這些修改。在orders表例子中,是添加e-mail列到表與移除列的回溯兩部分。現在,要回溯一個schema,我們只要簡單地告訴Rails我們需要數據庫schema序號就可以了。如果當前數據庫schema有個比目標序號更高的序號,則Rails會接受帶有數據庫當前序號的migration遷移,并使用它進行回溯。這會從schema中移除migration遷移的修改,并降低數據庫的序號。它會反復執行此過程直到得到期望的數據庫版本。 16.1 創建與運行Migration遷移
? Migration遷移是應用程序db/migrate目錄下的一個簡單的Ruby源文件。每個migration遷移文件的名字(默認地)以三個數字和一個下劃線開頭。這些數字是migration遷移的關鍵,因為它們定義了應用哪個migration遷移的次序,它們各個migration遷移的版本號。 Depot應用程序的db/migrate目錄看起像這樣: depot> ls db/migrate 001_create_products.rb 005_create_orders.rb 002_add_price.rb 006_create_line_items.rb 003_add_test_data.rb 007_create_users.rb 004_add_sessions.rb 雖然你可以通過手工來創建這些migration遷移文件,但使用一個生成器會更容易(這會減少錯誤)。就像在創建Depot應用程序時看到的,實際上有兩種用于創建migration遷移文件的生成器: 1、模型生成器(model generator) 創建的migration遷移,用于創建與模型關聯的表(只要你不使用skip-migrations選項)。如下面例子所示,創建了名為discount的模型,同時也創建了名為add_create_discounts.rb的migration遷移。 depot> ruby script/generate model discount exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/discount.rb create test/unit/discount_test.rb create test/fixtures/discounts.yml exists db/migrate create db/migrate/014_create_discounts.rb 2、你也可以用生成器只創建migration遷移本身。 depot> script/generate migration add_price_column exists db/migrate create db/migrate/015_add_price_column.rb 稍后,我們會從Migration的解剖圖開始,我們會看到migration遷移文件是什么。但現在,我們還是先看看如何運行migration遷移。 一、Running Migrations 使用Rake的db:migrate任務來運行migration遷移。 depot> rake db:migrate 下面看看發生了什么,讓我們深入到Rails的內部。 在Rails數據庫內部,migration遷移代碼管理一個名為schema_info的表。該表只有一個列,名為version,并且它只有一行記錄。此schema_info表被用于記住當前的數據庫版本。 當運行rake db:migrate時,任務首先會查看schema_info表。如果該表不存在,它將創建一個并且生成版本號為0的記錄。若該表存在,則從表中讀取版本號。 然后migration遷移代碼查看db/migrate目錄下的所有migration遷移文件。如果有序號(文件名的前導數字)大于當前數據庫的版本號,那么會為數據庫依次應用每個migration遷移文件。在最后一個migration遷移文件應用后,schema_info表的版本號會被更新為該文件的序號。 如果我們這一序號上再次運行migration遷移,則不會發生任何事。因為數據庫內版本號已等于最高序號migration遷移文件的序號,所以不會應用任何migration遷移文件。 但是,如果我們隨后創建了一個新的migration遷移文件,它就會有個大于數據庫版本號的序號。如果我們隨后運行該migration,則這個新的migration遷移文件會被運行。 你可以通過給rake db:migrate命令行應用VERSION=參數來為數據庫強制指定一個特定的版本號。如果你給出的版本號高于數據庫的版本號,則會以運行數據庫版本的migration開始,以你指定的版本migration結束。 但是,如果命令行的版本號小于當前數據庫的版本號,那么會發生不同的事情。在這些情形下,Rails查找與數據庫版本匹配的migration遷移文件,并且回溯它。然后它降低版本號,查找匹配文件,再回溯它,等等,直到版本號匹配你在命令行上指定版本號。也就是說,migration遷移被向后應用以回溯schema回到你指定版本。 16.2 Migration解剖圖
? 你通過創建Rails類ActiveRecord::Migration的子類來寫一個migration遷移。你創建的類至少應該包含兩個類方法up()和down()。 class SomeMeaningfulname < ActiveRecord::Migration def self.up # ... end def self.down # ... end end 在down()方法回溯修改的同時,up()方法有責任為這個migration遷移應用schema修改。讓我們做的更具體些。這兒是個migration遷移,它為orders表添加一個e_mails列。 class AddEmailColumnToOrders < ActiveRecord::Migration def self.up add_column :orders, :e_mail, :string end def self.down remove_column :orders, :e_mail end end 看看down()方法的回溯是如何影響到up()方法的。 一、Column Types (列類型) 傳遞給add_column的第三個參數指定了數據庫列的類型。 在前面例子中,我們指定e_mail列是 :string 類型。但這么做意味著什么呢?典型地數據庫沒有 :string 列類型。 回憶一下,Rails試圖讓你的應用程序不依賴于支持它運行的數據庫:你可以開發使用MySQL,如果愿意也可開使用Postgres數據庫的應用用程序。但不同的數據庫為列類型使用了不同的名字。如果你在migration遷移中使用了一個MySQL列類型,那么這個migration遷移對Postgres數據庫就不會工作。所以Rails migration遷移將你從基礎數據庫類型系統隔離開而使用邏輯類型。如果你遷移一個MySQL數據庫,那么 :string 類型將創建一個varchar(255)類型的列。在Postgres上,同樣的遷移會添加一個varing(255)類型的列。 由migration遷移支持的類型是: :binary,:boolean,:date,:datetime,:float,:integer,:string,:text,:time,和:timestamp。256頁的圖16-1顯示了在Rails內數據庫適配器對這些類型的缺省映射。使用這個圖,你在一個migration遷移內,用 :integer聲明的列在MySQL內基于類型int(11),在Oracle內基于number(38)來工作。 當在一個migration遷移內定義一個列時,你可指定三個選項。每個選項由key=>value對給出。 1、:null => true or false 如果為 true,則基礎列被添加一個不能為null的約束(如果數據庫支持的話)。 2、:limit => size 設置字段尺寸的限制。這基本上出現在用string創建數據庫的列時。 3、:default => value 為列設置缺省值。如果你傳遞一個Ruby值(或Ruby表達式),那個值會變成列的默認值。對一些數據庫,你也可以傳遞一個包含數據庫指定表達式的字符串。例如,指定 add_column :orders, :placed_at, :datetime, :default => Time.now 將在migration遷移運行時,設置列的默認值為日期和時間,指定add_column :orders, :placed_at, :datetime, :default => "now()" 會設置MySQL now()函數為默認值,因此當前日期時間將被插入到任何新的行內。后面的語法很明顯是數據庫指定的。 下面是一些使用migration遷移類型和選項的例子: add_column :orders, :name, :string, :limit => 100, :null => false add_column :orders, :age, :integer add_column :orders, :ship_class, :string, :limit => 15, :default => 'priority' 二、Renaming Columns (重命名列) 在我們重構代碼時,通常會修改我們變量的名字,以讓它們更有意義。Rails的migration遷移也允許我們對數據庫的列名字這樣做。例如,在添加了一周之后,我們可能認為e_mail并不是那個新列最好的名字。我們可以創建一個migration遷移來重命名它。 class RenameEmailColumn < ActiveRecord::Migration def self.up rename_column :orders, :e_mail, :customer_email end def self.down rename_column :orders, :customer_email, :e_mail end end 注意重命名列并不會刪除任何與該列相關的數據。但是要小心并不是所有的數據庫適配器都支持重命名的。 三、Changing Columns (更改列) 有時候你可能需要更改列的類型,或改變與列關聯的選項。它與你使用add_column的方式一樣,但指定是一個現有列的名字。我們假設order類型列當前是個integer,但我們需要更改它為string。我們想保持現有數據,所以一個123的order類型數據將變成字符串”123”。隨后,我們就可使用非整數值如”new”和”existing”。 Changing from an integer column to a string is easy: def self.up change_column :orders, :order_type, :string, :null => false end 但是,向相反方向的轉換卻是個問題。我們可能會試著這樣寫down() migration遷移: def self.down change_column :orders, :order_type, :integer end 但是如果我們的應用程序已在這個列中存儲了像”new”這樣的數據,down()方法將會丟失數據 --- “new”不能被轉換成一個整數。如果這是可接受的,那么migration遷移就接受它。然而,如果我們想創建一個單向的migration遷移 --- 那么它就是不可逆轉的 --- 你會中止向下的migration遷移。在這種情況下,Rails提供了一個你可以拋出的特殊異常。 class ChangeOrderTypeToString < ActiveRecord::Migration def self.up change_column :orders, :order_type, :string, :null => false end def self.down raise ActiveRecord::IrreversibleMigration end end
? Migration遷移是應用程序db/migrate目錄下的一個簡單的Ruby源文件。每個migration遷移文件的名字(默認地)以三個數字和一個下劃線開頭。這些數字是migration遷移的關鍵,因為它們定義了應用哪個migration遷移的次序,它們各個migration遷移的版本號。 Depot應用程序的db/migrate目錄看起像這樣: depot> ls db/migrate 001_create_products.rb 005_create_orders.rb 002_add_price.rb 006_create_line_items.rb 003_add_test_data.rb 007_create_users.rb 004_add_sessions.rb 雖然你可以通過手工來創建這些migration遷移文件,但使用一個生成器會更容易(這會減少錯誤)。就像在創建Depot應用程序時看到的,實際上有兩種用于創建migration遷移文件的生成器: 1、模型生成器(model generator) 創建的migration遷移,用于創建與模型關聯的表(只要你不使用skip-migrations選項)。如下面例子所示,創建了名為discount的模型,同時也創建了名為add_create_discounts.rb的migration遷移。 depot> ruby script/generate model discount exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/discount.rb create test/unit/discount_test.rb create test/fixtures/discounts.yml exists db/migrate create db/migrate/014_create_discounts.rb 2、你也可以用生成器只創建migration遷移本身。 depot> script/generate migration add_price_column exists db/migrate create db/migrate/015_add_price_column.rb 稍后,我們會從Migration的解剖圖開始,我們會看到migration遷移文件是什么。但現在,我們還是先看看如何運行migration遷移。 一、Running Migrations 使用Rake的db:migrate任務來運行migration遷移。 depot> rake db:migrate 下面看看發生了什么,讓我們深入到Rails的內部。 在Rails數據庫內部,migration遷移代碼管理一個名為schema_info的表。該表只有一個列,名為version,并且它只有一行記錄。此schema_info表被用于記住當前的數據庫版本。 當運行rake db:migrate時,任務首先會查看schema_info表。如果該表不存在,它將創建一個并且生成版本號為0的記錄。若該表存在,則從表中讀取版本號。 然后migration遷移代碼查看db/migrate目錄下的所有migration遷移文件。如果有序號(文件名的前導數字)大于當前數據庫的版本號,那么會為數據庫依次應用每個migration遷移文件。在最后一個migration遷移文件應用后,schema_info表的版本號會被更新為該文件的序號。 如果我們這一序號上再次運行migration遷移,則不會發生任何事。因為數據庫內版本號已等于最高序號migration遷移文件的序號,所以不會應用任何migration遷移文件。 但是,如果我們隨后創建了一個新的migration遷移文件,它就會有個大于數據庫版本號的序號。如果我們隨后運行該migration,則這個新的migration遷移文件會被運行。 你可以通過給rake db:migrate命令行應用VERSION=參數來為數據庫強制指定一個特定的版本號。如果你給出的版本號高于數據庫的版本號,則會以運行數據庫版本的migration開始,以你指定的版本migration結束。 但是,如果命令行的版本號小于當前數據庫的版本號,那么會發生不同的事情。在這些情形下,Rails查找與數據庫版本匹配的migration遷移文件,并且回溯它。然后它降低版本號,查找匹配文件,再回溯它,等等,直到版本號匹配你在命令行上指定版本號。也就是說,migration遷移被向后應用以回溯schema回到你指定版本。 16.2 Migration解剖圖
? 你通過創建Rails類ActiveRecord::Migration的子類來寫一個migration遷移。你創建的類至少應該包含兩個類方法up()和down()。 class SomeMeaningfulname < ActiveRecord::Migration def self.up # ... end def self.down # ... end end 在down()方法回溯修改的同時,up()方法有責任為這個migration遷移應用schema修改。讓我們做的更具體些。這兒是個migration遷移,它為orders表添加一個e_mails列。 class AddEmailColumnToOrders < ActiveRecord::Migration def self.up add_column :orders, :e_mail, :string end def self.down remove_column :orders, :e_mail end end 看看down()方法的回溯是如何影響到up()方法的。 一、Column Types (列類型) 傳遞給add_column的第三個參數指定了數據庫列的類型。 在前面例子中,我們指定e_mail列是 :string 類型。但這么做意味著什么呢?典型地數據庫沒有 :string 列類型。 回憶一下,Rails試圖讓你的應用程序不依賴于支持它運行的數據庫:你可以開發使用MySQL,如果愿意也可開使用Postgres數據庫的應用用程序。但不同的數據庫為列類型使用了不同的名字。如果你在migration遷移中使用了一個MySQL列類型,那么這個migration遷移對Postgres數據庫就不會工作。所以Rails migration遷移將你從基礎數據庫類型系統隔離開而使用邏輯類型。如果你遷移一個MySQL數據庫,那么 :string 類型將創建一個varchar(255)類型的列。在Postgres上,同樣的遷移會添加一個varing(255)類型的列。 由migration遷移支持的類型是: :binary,:boolean,:date,:datetime,:float,:integer,:string,:text,:time,和:timestamp。256頁的圖16-1顯示了在Rails內數據庫適配器對這些類型的缺省映射。使用這個圖,你在一個migration遷移內,用 :integer聲明的列在MySQL內基于類型int(11),在Oracle內基于number(38)來工作。 當在一個migration遷移內定義一個列時,你可指定三個選項。每個選項由key=>value對給出。 1、:null => true or false 如果為 true,則基礎列被添加一個不能為null的約束(如果數據庫支持的話)。 2、:limit => size 設置字段尺寸的限制。這基本上出現在用string創建數據庫的列時。 3、:default => value 為列設置缺省值。如果你傳遞一個Ruby值(或Ruby表達式),那個值會變成列的默認值。對一些數據庫,你也可以傳遞一個包含數據庫指定表達式的字符串。例如,指定 add_column :orders, :placed_at, :datetime, :default => Time.now 將在migration遷移運行時,設置列的默認值為日期和時間,指定add_column :orders, :placed_at, :datetime, :default => "now()" 會設置MySQL now()函數為默認值,因此當前日期時間將被插入到任何新的行內。后面的語法很明顯是數據庫指定的。 下面是一些使用migration遷移類型和選項的例子: add_column :orders, :name, :string, :limit => 100, :null => false add_column :orders, :age, :integer add_column :orders, :ship_class, :string, :limit => 15, :default => 'priority' 二、Renaming Columns (重命名列) 在我們重構代碼時,通常會修改我們變量的名字,以讓它們更有意義。Rails的migration遷移也允許我們對數據庫的列名字這樣做。例如,在添加了一周之后,我們可能認為e_mail并不是那個新列最好的名字。我們可以創建一個migration遷移來重命名它。 class RenameEmailColumn < ActiveRecord::Migration def self.up rename_column :orders, :e_mail, :customer_email end def self.down rename_column :orders, :customer_email, :e_mail end end 注意重命名列并不會刪除任何與該列相關的數據。但是要小心并不是所有的數據庫適配器都支持重命名的。 三、Changing Columns (更改列) 有時候你可能需要更改列的類型,或改變與列關聯的選項。它與你使用add_column的方式一樣,但指定是一個現有列的名字。我們假設order類型列當前是個integer,但我們需要更改它為string。我們想保持現有數據,所以一個123的order類型數據將變成字符串”123”。隨后,我們就可使用非整數值如”new”和”existing”。 Changing from an integer column to a string is easy: def self.up change_column :orders, :order_type, :string, :null => false end 但是,向相反方向的轉換卻是個問題。我們可能會試著這樣寫down() migration遷移: def self.down change_column :orders, :order_type, :integer end 但是如果我們的應用程序已在這個列中存儲了像”new”這樣的數據,down()方法將會丟失數據 --- “new”不能被轉換成一個整數。如果這是可接受的,那么migration遷移就接受它。然而,如果我們想創建一個單向的migration遷移 --- 那么它就是不可逆轉的 --- 你會中止向下的migration遷移。在這種情況下,Rails提供了一個你可以拋出的特殊異常。 class ChangeOrderTypeToString < ActiveRecord::Migration def self.up change_column :orders, :order_type, :string, :null => false end def self.down raise ActiveRecord::IrreversibleMigration end end
總結
以上是生活随笔為你收集整理的Rails Migrations的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SAP数据表(一)商品表
- 下一篇: 结构和类中字段的初始化以及用new来操作