1 遷移概觀
遷移是一種方便的方式,可以以可重現的方式隨著時間的推移演進您的資料庫結構。它們使用 Ruby DSL,因此您不必手動撰寫 SQL,從而讓您的結構和變更獨立於資料庫。我們建議您閱讀 Active Record 基礎和 Active Record 關聯的指南,以深入了解此處提及的一些概念。
您可以將每個遷移視為資料庫的新「版本」。結構從一無所有開始,每個遷移都會修改它以新增或移除表格、資料行或索引。Active Record 知道如何沿著此時間軸更新您的結構,使其從歷史上的任何點帶到最新版本。閱讀更多關於 Rails 如何知道要執行時間軸中的哪個遷移 的資訊。
Active Record 會更新您的 db/schema.rb
檔案,以符合您資料庫的最新結構。以下是一個遷移的範例
# db/migrate/20240502100843_create_products.rb
class CreateProducts < ActiveRecord::Migration[8.0]
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
此遷移新增一個名為 products
的表格,其中包含一個名為 name
的字串資料行和一個名為 description
的文字資料行。一個名為 id
的主鍵資料行也會隱式新增,因為它是所有 Active Record 模型預設的主鍵。timestamps
巨集會新增兩個資料行 created_at
和 updated_at
。如果這些特殊資料行存在,Active Record 會自動管理它們。
# db/schema.rb
ActiveRecord::Schema[8.0].define(version: 2024_05_02_100843) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "products", force: :cascade do |t|
t.string "name"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
我們定義了我們希望隨著時間推移發生的變更。在執行此遷移之前,不會有表格。執行之後,表格將存在。Active Record 也知道如何反轉此遷移;如果我們回溯此遷移,它將移除表格。在 回溯章節 中閱讀更多關於回溯遷移的資訊。
在定義我們希望隨著時間推移發生的變更之後,必須考慮遷移的可逆性。雖然 Active Record 可以管理遷移的向前進展,確保表格的建立,但可逆性的概念變得至關重要。透過可逆遷移,遷移不僅在應用時會建立表格,還能實現平穩的回溯功能。如果還原上述遷移,Active Record 會智慧地處理表格的移除,從而在整個過程中維持資料庫的一致性。請參閱 反轉遷移章節 以取得更多詳細資訊。
2 產生遷移檔案
2.1 建立獨立遷移
遷移以檔案的形式儲存在 db/migrate
目錄中,每個遷移類別一個檔案。
檔案名稱的形式為 YYYYMMDDHHMMSS_create_products.rb
,其中包含識別遷移的 UTC 時間戳記,後接底線,然後是遷移的名稱。遷移類別的名稱(駱駝命名法版本)應與檔案名稱的後一部分相符。
例如,20240502100843_create_products.rb
應定義類別 CreateProducts
,而 20240502101659_add_details_to_products.rb
應定義類別 AddDetailsToProducts
。Rails 使用此時間戳記來判斷應執行哪個遷移以及執行順序,因此如果您從其他應用程式複製遷移或自行產生檔案,請注意其在順序中的位置。您可以在 Rails 遷移版本控制章節 中閱讀更多關於如何使用時間戳記的資訊。
產生遷移時,Active Record 會自動將目前的時間戳記附加到遷移的檔案名稱。例如,執行以下命令將建立一個空的遷移檔案,其中檔案名稱由附加到遷移底線名稱的時間戳記組成。
$ bin/rails generate migration AddPartNumberToProducts
# db/migrate/20240502101659_add_part_number_to_products.rb
class AddPartNumberToProducts < ActiveRecord::Migration[8.0]
def change
end
end
產生器可以做的遠不止在檔案名稱中附加時間戳記。根據命名慣例和其他(可選)參數,它也可以開始充實遷移。
以下各節將介紹您可以根據慣例和其他參數建立遷移的各種方式。
2.2 建立新表格
當您想要在資料庫中建立新表格時,可以使用格式為「CreateXXX」的遷移,後接資料行名稱和類型清單。這將產生一個遷移檔案,其中會設定具有指定資料行的表格。
$ bin/rails generate migration CreateProducts name:string part_number:string
產生
class CreateProducts < ActiveRecord::Migration[8.0]
def change
create_table :products do |t|
t.string :name
t.string :part_number
t.timestamps
end
end
end
產生的檔案及其內容只是一個起點,您可以透過編輯 db/migrate/YYYYMMDDHHMMSS_create_products.rb
檔案,視需要新增或移除。
2.3 新增資料行
當您想要在資料庫的現有表格中新增新資料行時,可以使用格式為「AddColumnToTable」的遷移,後接資料行名稱和類型清單。這將產生一個遷移檔案,其中包含適當的 add_column
陳述式。
$ bin/rails generate migration AddPartNumberToProducts part_number:string
這將產生以下遷移
class AddPartNumberToProducts < ActiveRecord::Migration[8.0]
def change
add_column :products, :part_number, :string
end
end
如果您想要在新資料行上新增索引,您也可以這樣做。
$ bin/rails generate migration AddPartNumberToProducts part_number:string:index
這將產生適當的 add_column
和 add_index
陳述式
class AddPartNumberToProducts < ActiveRecord::Migration[8.0]
def change
add_column :products, :part_number, :string
add_index :products, :part_number
end
end
您不僅限於一個神奇產生的資料行。例如
$ bin/rails generate migration AddDetailsToProducts part_number:string price:decimal
這將會產生一個綱要遷移,在 products
資料表中新增兩個額外的欄位。
class AddDetailsToProducts < ActiveRecord::Migration[8.0]
def change
add_column :products, :part_number, :string
add_column :products, :price, :decimal
end
end
2.4 移除欄位
同樣地,如果遷移名稱的形式為 "RemoveColumnFromTable",並且後面接著欄位名稱和類型的列表,則會建立一個包含適當 remove_column
陳述式的遷移。
$ bin/rails generate migration RemovePartNumberFromProducts part_number:string
這將會產生適當的 remove_column
陳述式
class RemovePartNumberFromProducts < ActiveRecord::Migration[8.0]
def change
remove_column :products, :part_number, :string
end
end
2.5 建立關聯
Active Record 關聯用於定義應用程式中不同模型之間的關係,使其能夠透過彼此的關係進行互動,並更輕鬆地處理相關資料。若要了解更多關於關聯的資訊,您可以參考關聯基礎指南。
關聯的一個常見用例是在表格之間建立外鍵參考。產生器接受諸如 references
之類的欄位類型來促進此過程。References 是建立欄位、索引、外鍵,甚至是多型關聯欄位的簡寫。
例如,
$ bin/rails generate migration AddUserRefToProducts user:references
產生以下 add_reference
呼叫
class AddUserRefToProducts < ActiveRecord::Migration[8.0]
def change
add_reference :products, :user, null: false, foreign_key: true
end
end
上述遷移在 products
資料表中建立一個名為 user_id
的外鍵,其中 user_id
是對 users
資料表中 id
欄位的參考。它還為 user_id
欄位建立索引。綱要如下所示
create_table "products", force: :cascade do |t|
t.bigint "user_id", null: false
t.index ["user_id"], name: "index_products_on_user_id"
end
belongs_to
是 references
的別名,因此上述內容也可以寫成
$ bin/rails generate migration AddUserRefToProducts user:belongs_to
產生與上述相同的遷移和綱要。
如果名稱中包含 JoinTable
,則還有一個產生器會產生連接表格
$ bin/rails generate migration CreateJoinTableUserProduct user product
將產生以下遷移
class CreateJoinTableUserProduct < ActiveRecord::Migration[8.0]
def change
create_join_table :users, :products do |t|
# t.index [:user_id, :product_id]
# t.index [:product_id, :user_id]
end
end
end
2.6 其他建立遷移的產生器
除了 migration
產生器之外,model
、resource
和 scaffold
產生器也會建立適合新增模型的遷移。此遷移已經包含建立相關表格的指示。如果您告訴 Rails 您想要的欄位,則也會建立新增這些欄位的陳述式。例如,執行
$ bin/rails generate model Product name:string description:text
這將會建立一個如下所示的遷移
class CreateProducts < ActiveRecord::Migration[8.0]
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
您可以附加任意多個欄位名稱/類型配對。
2.7 傳遞修飾詞
產生遷移時,您可以直接在命令列上傳遞常用的類型修飾詞。這些修飾詞以大括號括起來,並遵循欄位類型,讓您可以自訂資料庫欄位的特性,而無需在之後手動編輯遷移檔案。
例如,執行
$ bin/rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic}
將產生如下所示的遷移
class AddDetailsToProducts < ActiveRecord::Migration[8.0]
def change
add_column :products, :price, :decimal, precision: 5, scale: 2
add_reference :products, :supplier, polymorphic: true
end
end
可以使用 !
快捷方式從命令列強制執行 NOT NULL
限制
$ bin/rails generate migration AddEmailToUsers email:string!
將產生此遷移
class AddEmailToUsers < ActiveRecord::Migration[8.0]
def change
add_column :users, :email, :string, null: false
end
end
若要取得產生器的進一步說明,請執行 bin/rails generate --help
。或者,您也可以執行 bin/rails generate model --help
或 bin/rails generate migration --help
以取得特定產生器的說明。
3 更新遷移
使用上述章節中的其中一個產生器建立遷移檔案後,您可以在 db/migrate
資料夾中更新產生的遷移檔案,以定義您想要對資料庫綱要進行的進一步變更。
3.1 建立表格
create_table
方法是最基本的遷移類型之一,但大多數時候,它會透過使用模型、資源或 scaffold 產生器為您產生。典型的用法是
create_table :products do |t|
t.string :name
end
此方法會建立一個具有名為 name
的欄位的 products
表格。
3.1.1 關聯
如果您要為具有關聯的模型建立表格,您可以使用 :references
類型來建立適當的欄位類型。例如
create_table :products do |t|
t.references :category
end
這將會建立一個 category_id
欄位。或者,您可以使用 belongs_to
作為 references
的別名
create_table :products do |t|
t.belongs_to :category
end
您也可以使用 :polymorphic
選項來指定欄位類型和索引建立
create_table :taggings do |t|
t.references :taggable, polymorphic: true
end
這將會建立 taggable_id
、taggable_type
欄位和適當的索引。
3.1.2 主索引鍵
預設情況下,create_table
會隱式地為您建立一個名為 id
的主索引鍵。您可以使用 :primary_key
選項來變更欄位名稱,如下所示
class CreateUsers < ActiveRecord::Migration[8.0]
def change
create_table :users, primary_key: "user_id" do |t|
t.string :username
t.string :email
t.timestamps
end
end
end
這將產生以下綱要
create_table "users", primary_key: "user_id", force: :cascade do |t|
t.string "username"
t.string "email"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
您也可以將陣列傳遞給 :primary_key
作為複合主索引鍵。閱讀更多關於複合主索引鍵的資訊。
class CreateUsers < ActiveRecord::Migration[8.0]
def change
create_table :users, primary_key: [:id, :name] do |t|
t.string :name
t.string :email
t.timestamps
end
end
end
如果您完全不需要主索引鍵,您可以傳遞 id: false
選項。
class CreateUsers < ActiveRecord::Migration[8.0]
def change
create_table :users, id: false do |t|
t.string :username
t.string :email
t.timestamps
end
end
end
3.1.3 資料庫選項
如果您需要傳遞資料庫特定的選項,您可以將 SQL 片段放在 :options
選項中。例如
create_table :products, options: "ENGINE=BLACKHOLE" do |t|
t.string :name, null: false
end
這會將 ENGINE=BLACKHOLE
附加到用於建立表格的 SQL 陳述式。
可以透過將 index: true
或選項雜湊傳遞給 :index
選項,在 create_table
區塊中建立的欄位上建立索引
create_table :users do |t|
t.string :name, index: true
t.string :email, index: { unique: true, name: "unique_emails" }
end
3.1.4 註解
您可以傳遞 :comment
選項,其中包含將儲存在資料庫本身中,並且可以使用資料庫管理工具(例如 MySQL Workbench 或 PgAdmin III)檢視的任何表格描述。註解可以幫助團隊成員更好地了解資料模型,並在具有大型資料庫的應用程式中產生文件。目前只有 MySQL 和 PostgreSQL 配接器支援註解。
class AddDetailsToProducts < ActiveRecord::Migration[8.0]
def change
add_column :products, :price, :decimal, precision: 8, scale: 2, comment: "The price of the product in USD"
add_column :products, :stock_quantity, :integer, comment: "The current stock quantity of the product"
end
end
3.2 建立連接表格
遷移方法 create_join_table
建立一個 HABTM(has and belongs to many) 連接表格。典型的用法是
create_join_table :products, :categories
此遷移將會建立一個具有兩個名為 category_id
和 product_id
的欄位的 categories_products
表格。
這些欄位的選項 :null
預設設為 false
,這表示您必須提供一個值,才能將記錄儲存到此表格。這可以透過指定 :column_options
選項來覆寫
create_join_table :products, :categories, column_options: { null: true }
預設情況下,連接表格的名稱來自提供給 create_join_table 的前兩個引數的聯合,以詞彙順序排列。在此情況下,表格將被命名為 categories_products
。
模型名稱之間的優先順序是使用 String
的 <=>
運算子計算的。這表示如果字串的長度不同,並且在比較到最短長度時字串相等,則較長的字串會被認為比短字串具有較高的詞彙優先順序。例如,人們會期望表格 "paper_boxes" 和 "papers" 產生一個名為 "papers_paper_boxes" 的連接表格名稱,因為名稱 "paper_boxes" 的長度,但實際上它會產生一個名為 "paper_boxes_papers" 的連接表格名稱(因為在常見的編碼中,底線 '_' 在詞彙上小於 's')。
若要自訂表格的名稱,請提供 :table_name
選項
create_join_table :products, :categories, table_name: :categorization
這會建立一個名為 categorization
的連接表格。
此外,create_join_table
接受一個區塊,您可以使用它來新增索引(預設情況下不會建立)或您選擇的任何其他欄位。
create_join_table :products, :categories do |t|
t.index :product_id
t.index :category_id
end
3.3 變更表格
如果您想要就地變更現有的表格,則有 change_table
。
它的使用方式與 create_table
類似,但區塊內部產生的物件可以存取許多特殊函式,例如
change_table :products do |t|
t.remove :description, :name
t.string :part_number
t.index :part_number
t.rename :upccode, :upc_code
end
此遷移將會移除 description
和 name
欄位,建立一個名為 part_number
的新字串欄位,並在其上新增一個索引。最後,它會將 upccode
欄位重新命名為 upc_code
。
3.4 變更欄位
與我們先前介紹的 remove_column
和 add_column
方法類似,Rails 也提供了 change_column
遷移方法。
change_column :products, :part_number, :text
這會將產品表格上的 part_number
欄位變更為 :text
欄位。
change_column
命令是不可逆的。為了確保您的遷移可以安全地還原,您需要提供您自己的 reversible
遷移。請參閱可還原遷移章節以取得更多詳細資訊。
除了 change_column
之外,change_column_null
和 change_column_default
方法用於變更欄位的 null 限制和預設值。
change_column_default :products, :approved, from: true, to: false
這會將 :approved
欄位的預設值從 true 變更為 false。此變更只會套用至未來的記錄,任何現有的記錄都不會變更。請使用 change_column_null
來變更 null 限制。
change_column_null :products, :name, false
這會將產品上的 :name
欄位設定為 NOT NULL
欄位。此變更也會套用到現有的記錄,因此您需要確保所有現有的記錄都有一個 NOT NULL
的 :name
。
將 null 限制設定為 true
表示欄位將接受 null 值,否則會套用 NOT NULL
限制,並且必須傳入一個值,才能將記錄保存到資料庫。
您也可以將上述 change_column_default
遷移寫成 change_column_default :products, :approved, false
,但與之前的範例不同,這會使您的遷移無法還原。
3.5 欄位修飾詞
欄位修飾詞可以在建立或變更欄位時套用
comment
為欄位新增註解。collation
指定string
或text
欄位的定序。default
允許在欄位上設定預設值。請注意,如果您使用動態值(例如日期),則預設值只會在第一次計算(即在套用遷移的日期)。使用nil
表示NULL
。limit
設定string
欄位的最大字元數,以及text/binary/integer
欄位的最大位元組數。null
允許或不允許欄位中的NULL
值。precision
指定decimal/numeric/datetime/time
欄位的精確度。scale
指定decimal
和numeric
欄位的小數位數,代表小數點後的位數。
對於 add_column
或 change_column
,沒有新增索引的選項。它們需要使用 add_index
單獨新增。
某些适配器可能支持额外的选项;有关详细信息,请参阅适配器特定的 API 文档。
在產生遷移時,無法透過命令列指定 default
。
3.6 參考
add_reference
方法允許建立適當命名的欄位,作為一個或多個關聯之間的連接。
add_reference :users, :role
此遷移將在 users 資料表中建立一個名為 role_id
的外鍵欄位。role_id
是對 roles
資料表中 id
欄位的參考。此外,它會為 role_id
欄位建立索引,除非明確告知不要使用 index: false
選項。
另請參閱 Active Record 關聯 指南以了解更多資訊。
方法 add_belongs_to
是 add_reference
的別名。
add_belongs_to :taggings, :taggable, polymorphic: true
多型選項將在 taggings 資料表上建立兩個欄位,可用於多型關聯:taggable_type
和 taggable_id
。
請參閱本指南以了解更多關於多型關聯的資訊。
可以使用 foreign_key
選項建立外鍵。
add_reference :users, :role, foreign_key: true
有關更多 add_reference
選項,請訪問 API 文件。
參考也可以被移除
remove_reference :products, :user, foreign_key: true, index: false
3.7 外鍵
雖然不是必須的,您可能想要新增外鍵約束以保證參考完整性。
add_foreign_key :articles, :authors
add_foreign_key
呼叫會在 articles
資料表中新增一個新的約束。該約束保證 authors
資料表中存在一行,其中 id
欄位與 articles.author_id
相符,以確保 articles
資料表中列出的所有審閱者都是 authors
資料表中列出的有效作者。
在遷移中使用 references
時,您正在資料表中建立一個新的欄位,並且您可以選擇使用 foreign_key: true
將外鍵新增到該欄位。但是,如果您想將外鍵新增到現有的欄位,可以使用 add_foreign_key
。
如果我們要新增外鍵的資料表的欄位名稱無法從具有參考主鍵的資料表推導出來,則可以使用 :column
選項來指定欄位名稱。此外,如果參考的主鍵不是 :id
,您可以使用 :primary_key
選項。
例如,要在 articles.reviewer
上新增一個參考 authors.email
的外鍵
add_foreign_key :articles, :authors, column: :reviewer, primary_key: :email
這將在 articles
資料表中新增一個約束,該約束保證 authors
資料表中存在一行,其中 email
欄位與 articles.reviewer
欄位相符。
add_foreign_key
還支援其他幾個選項,例如 name
、on_delete
、if_not_exists
、validate
和 deferrable
。
也可以使用 remove_foreign_key
移除外鍵
# let Active Record figure out the column name
remove_foreign_key :accounts, :branches
# remove foreign key for a specific column
remove_foreign_key :accounts, column: :owner_id
Active Record 僅支援單欄位外鍵。需要使用 execute
和 structure.sql
來使用複合外鍵。請參閱 Schema Dumping and You。
3.8 複合主鍵
有時,單一欄位的值不足以唯一識別資料表中的每一行,但兩個或多個欄位的組合確實可以唯一識別它。當使用沒有單一 id
欄位作為主鍵的舊版資料庫結構描述,或者為分片或多租戶更改結構描述時,可能會出現這種情況。
您可以通過將 :primary_key
選項傳遞給具有陣列值的 create_table
來建立具有複合主鍵的資料表
class CreateProducts < ActiveRecord::Migration[8.0]
def change
create_table :products, primary_key: [:customer_id, :product_sku] do |t|
t.integer :customer_id
t.string :product_sku
t.text :description
end
end
end
具有複合主鍵的資料表需要將陣列值而不是整數 ID 傳遞給許多方法。另請參閱 Active Record 複合主鍵 指南以了解更多資訊。
3.9 執行 SQL
如果 Active Record 提供的助手不足,您可以使用 execute
方法來執行 SQL 命令。例如,
class UpdateProductPrices < ActiveRecord::Migration[8.0]
def up
execute "UPDATE products SET price = 'free'"
end
def down
execute "UPDATE products SET price = 'original_price' WHERE price = 'free';"
end
end
在此範例中,我們將產品資料表的 price
欄位更新為所有記錄的「free」。
應謹慎地在遷移中直接修改資料。請考慮這是否是您的使用案例的最佳方法,並注意潛在的缺點,例如增加複雜性和維護負擔、資料完整性風險和資料庫可攜性。有關更多詳細資訊,請參閱資料遷移文件。
有關個別方法的更多詳細資訊和範例,請查閱 API 文件。
特別是 ActiveRecord::ConnectionAdapters::SchemaStatements
的文件,其中提供了 change
、up
和 down
方法中可用的方法。
有關 create_table
產生的物件可用的方法,請參閱 ActiveRecord::ConnectionAdapters::TableDefinition
。
對於 change_table
產生的物件,請參閱 ActiveRecord::ConnectionAdapters::Table
。
3.10 使用 change
方法
change
方法是撰寫遷移的主要方式。它適用於大多數 Active Record 知道如何自動反轉遷移操作的情況。以下是 change
支援的一些操作
add_check_constraint
add_column
add_foreign_key
add_index
add_reference
add_timestamps
change_column_comment
(必須提供:from
和:to
選項)change_column_default
(必須提供:from
和:to
選項)change_column_null
change_table_comment
(必須提供:from
和:to
選項)create_join_table
create_table
disable_extension
drop_join_table
drop_table
(必須提供資料表建立選項和區塊)enable_extension
remove_check_constraint
(必須提供原始約束表達式)remove_column
(必須提供原始類型和欄位選項)remove_columns
(必須提供原始類型和欄位選項)remove_foreign_key
(必須提供其他資料表和原始選項)remove_index
(必須提供欄位和原始選項)remove_reference
(必須提供原始選項)remove_timestamps
(必須提供原始選項)rename_column
rename_index
rename_table
change_table
也是可逆的,只要區塊僅呼叫如上列出的可逆操作。
如果您需要使用任何其他方法,則應使用 reversible
或撰寫 up
和 down
方法,而不是使用 change
方法。
3.11 使用 reversible
如果您希望遷移執行 Active Record 不知道如何反轉的操作,則可以使用 reversible
來指定在執行遷移時執行什麼操作,以及在還原遷移時執行什麼其他操作。
class ChangeProductsPrice < ActiveRecord::Migration[8.0]
def change
reversible do |direction|
change_table :products do |t|
direction.up { t.change :price, :string }
direction.down { t.change :price, :integer }
end
end
end
end
此遷移會將 price
欄位的類型變更為字串,或在還原遷移時變更回整數。請注意分別傳遞給 direction.up
和 direction.down
的區塊。
或者,您可以使用 up
和 down
而不是 change
class ChangeProductsPrice < ActiveRecord::Migration[8.0]
def up
change_table :products do |t|
t.change :price, :string
end
end
def down
change_table :products do |t|
t.change :price, :integer
end
end
end
此外,當執行原始 SQL 查詢或執行在 ActiveRecord 方法中沒有直接等效項的資料庫操作時,reversible
非常有用。您可以使用 reversible
來指定在執行遷移時執行什麼操作,以及在還原遷移時執行什麼其他操作。例如
class ExampleMigration < ActiveRecord::Migration[8.0]
def change
create_table :distributors do |t|
t.string :zipcode
end
reversible do |direction|
direction.up do
# create a distributors view
execute <<-SQL
CREATE VIEW distributors_view AS
SELECT id, zipcode
FROM distributors;
SQL
end
direction.down do
execute <<-SQL
DROP VIEW distributors_view;
SQL
end
end
add_column :users, :address, :string
end
end
使用 reversible
也會確保指令以正確的順序執行。如果還原先前的範例遷移,則會在移除 users.address
欄位之後,且在刪除 distributors
資料表之前執行 down
區塊。
3.12 使用 up
/down
方法
您也可以使用舊式的遷移方式,使用 up
和 down
方法而不是 change
方法。
up
方法應描述您想要對結構描述進行的轉換,而遷移的 down
方法應還原 up
方法完成的轉換。換句話說,如果您執行 up
然後執行 down
,資料庫結構描述應保持不變。
例如,如果您在 up
方法中建立一個資料表,則應在 down
方法中刪除它。明智的做法是以與在 up
方法中進行轉換完全相反的順序執行轉換。reversible
部分中的範例等效於
class ExampleMigration < ActiveRecord::Migration[8.0]
def up
create_table :distributors do |t|
t.string :zipcode
end
# create a distributors view
execute <<-SQL
CREATE VIEW distributors_view AS
SELECT id, zipcode
FROM distributors;
SQL
add_column :users, :address, :string
end
def down
remove_column :users, :address
execute <<-SQL
DROP VIEW distributors_view;
SQL
drop_table :distributors
end
end
3.13 拋出錯誤以防止還原
有時候您的遷移操作會執行一些無法回復的動作;例如,它可能會摧毀某些資料。
在這種情況下,您可以在 down
區塊中拋出 ActiveRecord::IrreversibleMigration
錯誤。
class IrreversibleMigrationExample < ActiveRecord::Migration[8.0]
def up
drop_table :example_table
end
def down
raise ActiveRecord::IrreversibleMigration, "This migration cannot be reverted because it destroys data."
end
end
如果有人嘗試還原您的遷移,將會顯示錯誤訊息,說明無法完成。
3.14 還原先前的遷移
您可以使用 Active Record 的能力,利用 revert
方法來回滾遷移。
require_relative "20121212123456_example_migration"
class FixupExampleMigration < ActiveRecord::Migration[8.0]
def change
revert ExampleMigration
create_table(:apples) do |t|
t.string :variety
end
end
end
revert
方法也接受一個程式碼區塊來反轉指令。這對於還原先前遷移的特定部分很有用。
例如,假設 ExampleMigration
已經提交,之後決定不再需要 Distributors 視圖。
class DontUseDistributorsViewMigration < ActiveRecord::Migration[8.0]
def change
revert do
# copy-pasted code from ExampleMigration
create_table :distributors do |t|
t.string :zipcode
end
reversible do |direction|
direction.up do
# create a distributors view
execute <<-SQL
CREATE VIEW distributors_view AS
SELECT id, zipcode
FROM distributors;
SQL
end
direction.down do
execute <<-SQL
DROP VIEW distributors_view;
SQL
end
end
# The rest of the migration was ok
end
end
end
相同的遷移也可以不使用 revert
來撰寫,但這會涉及更多步驟
- 反轉
create_table
和reversible
的順序。 - 將
create_table
替換為drop_table
。 - 最後,將
up
替換為down
,反之亦然。
這些都由 revert
處理。
4 執行遷移
Rails 提供了一組命令來執行特定的遷移集合。
您將使用的第一個與 rails 相關的遷移命令很可能是 bin/rails db:migrate
。在其最基本的形式中,它只會執行所有尚未執行的遷移的 change
或 up
方法。如果沒有這樣的遷移,它會退出。它將根據遷移的日期按順序執行這些遷移。
請注意,執行 db:migrate
命令也會調用 db:schema:dump
命令,這會更新您的 db/schema.rb
檔案以符合您的資料庫結構。
如果您指定目標版本,Active Record 將執行所需的遷移(change、up、down),直到它達到指定的版本。版本是遷移檔案名稱中的數字前綴。例如,要遷移到版本 20240428000000,請執行
$ bin/rails db:migrate VERSION=20240428000000
如果版本 20240428000000 大於目前版本(即,向上遷移),這將在所有遷移上執行 change
(或 up
)方法,直到並包括 20240428000000,並且不會執行任何較晚的遷移。如果向下遷移,這將在所有遷移上執行 down
方法,直到但不包括 20240428000000。
4.1 回滾
常見的任務是回滾最後一次遷移。例如,如果您在其中犯了一個錯誤並希望更正它。您可以執行以下命令,而無需追蹤與先前遷移相關的版本號碼
$ bin/rails db:rollback
這將回滾最新的遷移,無論是通過還原 change
方法還是執行 down
方法。如果您需要撤銷多個遷移,您可以提供 STEP
參數
$ bin/rails db:rollback STEP=3
將會還原最近 3 次的遷移。
在某些情況下,如果您修改了本地遷移並希望在再次向上遷移之前回滾該特定遷移,您可以使用 db:migrate:redo
命令。與 db:rollback
命令一樣,如果需要回溯多個版本,您可以使用 STEP
參數,例如
$ bin/rails db:migrate:redo STEP=3
您可以使用 db:migrate
獲得相同的結果。但是,它們的存在是為了方便,因此您無需明確指定要遷移到的版本。
4.1.1 事務
在支援 DDL 事務的資料庫中,在單個事務中變更結構描述時,每個遷移都包裝在一個事務中。
事務確保如果遷移在執行過程中失敗,則會回滾任何已成功應用的變更,從而保持資料庫的一致性。這表示事務中的所有操作要么都成功執行,要么都不執行,以防止在事務期間發生錯誤時資料庫處於不一致的狀態。
如果資料庫不支援具有變更結構描述語句的 DDL 事務,則當遷移失敗時,已成功的部分將不會回滾。您必須手動回滾變更。
但是,有些查詢您無法在事務中執行,對於這些情況,您可以使用 disable_ddl_transaction!
關閉自動事務。
class ChangeEnum < ActiveRecord::Migration[8.0]
disable_ddl_transaction!
def up
execute "ALTER TYPE model_size ADD VALUE 'new_value'"
end
end
請記住,即使您在具有 self.disable_ddl_transaction! 的遷移中,您仍然可以開啟自己的事務。
4.2 設定資料庫
bin/rails db:setup
命令將建立資料庫、載入結構描述,並使用種子資料初始化它。
4.3 準備資料庫
bin/rails db:prepare
命令與 bin/rails db:setup
類似,但它是冪等的,因此可以安全地多次調用,但它只會執行一次必要的任務。
- 如果尚未建立資料庫,則該命令將像
bin/rails db:setup
一樣執行。 - 如果資料庫存在但尚未建立表格,該命令將載入結構描述、執行任何待處理的遷移、傾印更新的結構描述,最後載入種子資料。有關更多詳細資訊,請參閱種子資料文件。
- 如果資料庫和表格都存在,則該命令將不執行任何操作。
資料庫和表格存在後,即使先前載入的種子資料或現有的種子檔案已變更或刪除,db:prepare
任務也不會嘗試重新載入種子資料。要重新載入種子資料,您可以手動執行 bin/rails db:seed
。
此任務僅在建立的資料庫或表格之一是環境的主要資料庫或配置為 seeds: true
時才會載入種子。
4.4 重設資料庫
bin/rails db:reset
命令將刪除資料庫並再次設定。這在功能上等同於 bin/rails db:drop db:setup
。
這與執行所有遷移不同。它只會使用目前 db/schema.rb
或 db/structure.sql
檔案的內容。如果無法回滾遷移,bin/rails db:reset
可能無法幫助您。要了解有關傾印結構描述的更多資訊,請參閱結構描述傾印與您章節。
4.5 執行特定的遷移
如果您需要向上或向下執行特定的遷移,db:migrate:up
和 db:migrate:down
命令可以做到。只需指定適當的版本,就會調用相應遷移的 change
、up
或 down
方法,例如
$ bin/rails db:migrate:up VERSION=20240428000000
通過執行此命令,將為版本為「20240428000000」的遷移執行 change
方法(或 up
方法)。
首先,此命令將檢查遷移是否存在,以及是否已執行過,如果已執行過,則不會執行任何操作。
如果指定的版本不存在,Rails 將拋出例外。
$ bin/rails db:migrate VERSION=00000000000000
rails aborted!
ActiveRecord::UnknownMigrationVersionError:
No migration with version number 00000000000000.
4.6 在不同的環境中執行遷移
預設情況下,執行 bin/rails db:migrate
將在 development
環境中執行。
要在另一個環境中執行遷移,您可以在執行命令時使用 RAILS_ENV
環境變數指定。例如,要在 test
環境中執行遷移,您可以執行
$ bin/rails db:migrate RAILS_ENV=test
4.7 變更執行遷移的輸出
預設情況下,遷移會告訴您它們正在做什麼以及花了多長時間。建立表格並新增索引的遷移可能會產生類似以下的輸出
== CreateProducts: migrating =================================================
-- create_table(:products)
-> 0.0028s
== CreateProducts: migrated (0.0028s) ========================================
遷移中提供了幾個方法,可讓您控制所有這些
方法 | 目的 |
---|---|
suppress_messages |
將區塊作為引數,並抑制區塊產生的任何輸出。 |
say |
將訊息引數作為原樣輸出。可以傳遞第二個布林引數,以指定是否要縮排。 |
say_with_time |
輸出文字以及執行其區塊所花費的時間。如果區塊傳回一個整數,它會假設它是受影響的列數。 |
例如,以下面的遷移為例
class CreateProducts < ActiveRecord::Migration[8.0]
def change
suppress_messages do
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
say "Created a table"
suppress_messages { add_index :products, :name }
say "and an index!", true
say_with_time "Waiting for a while" do
sleep 10
250
end
end
end
這將產生以下輸出
== CreateProducts: migrating =================================================
-- Created a table
-> and an index!
-- Waiting for a while
-> 10.0013s
-> 250 rows
== CreateProducts: migrated (10.0054s) =======================================
如果您希望 Active Record 不輸出任何內容,則執行 bin/rails db:migrate VERBOSE=false
將抑制所有輸出。
4.8 Rails 遷移版本控制
Rails 會通過資料庫中的 schema_migrations
表格來追蹤已執行的遷移。當您執行遷移時,Rails 會在 schema_migrations
表格中插入一行,其中包含遷移的版本號碼,儲存在 version
欄位中。這讓 Rails 能夠判斷哪些遷移已經應用到資料庫中。
例如,如果您有名為 20240428000000_create_users.rb 的遷移檔案,Rails 將從檔案名稱中提取版本號碼 (20240428000000),並在遷移成功執行後將其插入到 schema_migrations 表格中。
您可以在資料庫管理工具中或使用 Rails 控制台直接檢視 schema_migrations 表格的內容
rails dbconsole
然後,在資料庫控制台中,您可以查詢 schema_migrations 表格
SELECT * FROM schema_migrations;
這將顯示已應用到資料庫的所有遷移版本號碼的清單。當您執行 rails db:migrate 或 rails db:migrate:up 命令時,Rails 會使用此資訊來判斷需要執行哪些遷移。
5 變更現有遷移
有時您在編寫遷移時會犯錯。如果您已經執行了遷移,那麼您就不能只編輯遷移並再次執行遷移:Rails 會認為它已經執行了遷移,因此當您執行 bin/rails db:migrate
時不會執行任何操作。您必須回滾遷移(例如使用 bin/rails db:rollback
),編輯您的遷移,然後執行 bin/rails db:migrate
以執行更正的版本。
一般來說,編輯已提交到原始碼控制的現有遷移不是一個好主意。如果您和您的同事已經在生產機器上執行了現有的遷移版本,您將為自己和您的同事製造額外的工作,並造成嚴重的頭痛。相反,您應該編寫一個新的遷移來執行您需要的變更。
但是,編輯尚未提交到原始碼控制(或更常見的是,尚未傳播到您的開發機器之外)的新生成遷移是很常見的。
當編寫新的遷移以完全或部分撤銷先前的遷移時,revert
方法會很有幫助(請參閱上面的還原先前的遷移)。
6 結構描述傾印與您
6.1 結構描述檔案的用途是什麼?
遷移雖然強大,但並不是您的資料庫結構描述的權威來源。 您的資料庫仍然是事實的來源。
預設情況下,Rails 會產生 db/schema.rb
,它試圖捕獲您資料庫結構描述的目前狀態。
通過 bin/rails db:schema:load
載入結構描述檔案來建立應用程式資料庫的新實例,往往比重播整個遷移歷史記錄更快,並且更不容易出錯。舊遷移如果那些遷移使用了變更的外部相依性或依賴於與遷移分開發展的應用程式程式碼,則可能無法正確應用。
如果您想快速查看 Active Record 物件具有哪些屬性,結構描述檔案也很有用。此資訊不在模型的程式碼中,並且經常分散在多個遷移中,但該資訊在結構描述檔案中得到了很好的總結。
6.2 結構描述傾印的類型
Rails 產生的結構描述傾印的格式由 config/application.rb
中定義的 config.active_record.schema_format
設定控制。預設情況下,格式為 :ruby
,或者可以設定為 :sql
。
6.2.1 使用預設的 :ruby
結構描述
當選擇 :ruby
時,資料庫綱要會儲存在 db/schema.rb
中。如果你查看這個檔案,你會發現它看起來很像一個非常大的遷移。
ActiveRecord::Schema[8.0].define(version: 2008_09_06_171750) do
create_table "authors", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "products", force: true do |t|
t.string "name"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
t.string "part_number"
end
end
在許多方面,它確實就是這樣。這個檔案是透過檢查資料庫並使用 create_table
、add_index
等來表達其結構而建立的。
6.2.2 使用 :sql
綱要傾印器
然而,db/schema.rb
無法表達你的資料庫可能支援的所有內容,例如觸發器、序列、預存程序等。
雖然遷移可以使用 execute
來建立 Ruby 遷移 DSL 不支援的資料庫結構,但這些結構可能無法由綱要傾印器重新建立。
如果你正在使用這些功能,你應該將綱要格式設定為 :sql
,以便獲得一個準確的綱要檔案,用於建立新的資料庫實例。
當綱要格式設定為 :sql
時,資料庫結構將使用特定於資料庫的工具傾印到 db/structure.sql
中。例如,對於 PostgreSQL,會使用 pg_dump
工具。對於 MySQL 和 MariaDB,此檔案將包含各種資料表的 SHOW CREATE TABLE
的輸出。
若要從 db/structure.sql
載入綱要,請執行 bin/rails db:schema:load
。載入此檔案是透過執行其中包含的 SQL 語句來完成的。根據定義,這將建立一個資料庫結構的完美副本。
6.3 綱要傾印和原始碼控制
由於綱要檔案通常用於建立新的資料庫,因此強烈建議你將綱要檔案檢入原始碼控制。
當兩個分支修改綱要時,綱要檔案中可能會發生合併衝突。要解決這些衝突,請執行 bin/rails db:migrate
以重新產生綱要檔案。
新產生的 Rails 應用程式已經將遷移資料夾包含在 git 樹中,因此你只需要確保添加任何你新增的遷移並提交它們。
7 Active Record 和參照完整性
Active Record 模式建議智慧應主要存在於你的模型中,而不是在資料庫中。因此,觸發器或約束等將部分智慧委派回資料庫的功能,並不總是受到青睞。
諸如 validates :foreign_key, uniqueness: true
之類的驗證是模型可以強制執行資料完整性的一種方式。關聯上的 :dependent
選項允許模型在父物件被銷毀時自動銷毀子物件。與任何在應用程式層級運作的東西一樣,這些無法保證參照完整性,因此有些人會使用資料庫中的外鍵約束來擴充它們。
實際上,外鍵約束和唯一索引在資料庫層級強制執行時通常被認為更安全。雖然 Active Record 不直接支援使用這些資料庫層級的功能,你仍然可以使用 execute 方法來執行任意 SQL 命令。
值得強調的是,雖然 Active Record 模式強調將智慧保留在模型中,但忽略在資料庫層級實施外鍵和唯一約束可能會導致完整性問題。因此,建議在適當的情況下使用資料庫層級的約束來補充 AR 模式。這些約束應在你的程式碼中使用關聯和驗證來明確定義其對應項,以確保應用程式和資料庫層級的資料完整性。
8 遷移和種子資料
Rails 遷移功能的主要目的是發出命令,使用一致的過程修改綱要。遷移也可以用於新增或修改資料。這在現有的資料庫(例如生產資料庫)無法銷毀和重新建立時很有用。
class AddInitialProducts < ActiveRecord::Migration[8.0]
def up
5.times do |i|
Product.create(name: "Product ##{i}", description: "A product.")
end
end
def down
Product.delete_all
end
end
若要在建立資料庫後新增初始資料,Rails 有一個內建的「種子」功能可以加速此過程。這在開發和測試環境中經常重新載入資料庫,或在設定生產環境的初始資料時特別有用。
若要開始使用此功能,請開啟 db/seeds.rb
並新增一些 Ruby 程式碼,然後執行 bin/rails db:seed
。
此處的程式碼應該是等冪的,以便可以在每個環境中的任何時間點執行。
["Action", "Comedy", "Drama", "Horror"].each do |genre_name|
MovieGenre.find_or_create_by!(name: genre_name)
end
這通常是設定空白應用程式資料庫的更簡潔方法。
9 舊的遷移
db/schema.rb
或 db/structure.sql
是目前資料庫狀態的快照,也是重建該資料庫的權威來源。這使得刪除或修剪舊的遷移檔案成為可能。
當你刪除 db/migrate/
目錄中的遷移檔案時,任何在這些檔案仍然存在時執行過 bin/rails db:migrate
的環境,都會在名為 schema_migrations
的內部 Rails 資料庫資料表中保留對特定於它們的遷移時間戳記的參照。你可以在Rails 遷移版本控制部分中閱讀更多相關資訊。
如果你執行 bin/rails db:migrate:status
命令,它會顯示每個遷移的狀態(向上或向下),你應該會看到 ********** NO FILE **********
顯示在任何已刪除的遷移檔案旁邊,該檔案曾經在特定環境上執行過,但現在在 db/migrate/
目錄中找不到。
9.1 引擎中的遷移
處理來自 引擎的遷移時,需要考慮一個注意事項。從引擎安裝遷移的 Rake 任務是等冪的,這表示無論調用多少次,它們都會產生相同的結果。由於先前的安裝而存在於父應用程式中的遷移會被跳過,遺失的遷移會以新的前導時間戳記複製。如果你刪除舊的引擎遷移並再次執行安裝任務,你會得到具有新時間戳記的新檔案,而 db:migrate
會嘗試再次執行它們。
因此,你通常會希望保留來自引擎的遷移。它們有一個像這樣的特殊註解
# This migration comes from blorgh (originally 20210621082949)
10 其他
10.1 使用 UUID 而非 ID 作為主索引鍵
預設情況下,Rails 使用自動遞增整數作為資料庫記錄的主索引鍵。然而,在某些情況下,使用通用唯一識別碼 (UUID) 作為主索引鍵可能是有利的,尤其是在分散式系統中或需要與外部服務整合時。UUID 提供了一個全域唯一的識別碼,而無需依賴集中式機構來產生 ID。
10.1.1 在 Rails 中啟用 UUID
在你的 Rails 應用程式中使用 UUID 之前,你需要確保你的資料庫支援儲存它們。此外,你可能需要設定你的資料庫配接器以使用 UUID。
如果你使用的 PostgreSQL 版本早於 13,你可能仍然需要啟用 pgcrypto 擴充功能才能存取 gen_random_uuid()
函數。
Rails 設定
在你的 Rails 應用程式設定檔案 (
config/application.rb
) 中,新增以下程式碼以設定 Rails 預設產生 UUID 作為主索引鍵config.generators do |g| g.orm :active_record, primary_key_type: :uuid end
此設定指示 Rails 將 UUID 作為 ActiveRecord 模型的預設主索引鍵類型。
新增具有 UUID 的參照
當使用參照在模型之間建立關聯時,請確保你將資料類型指定為 :uuid,以與主索引鍵類型保持一致。例如
create_table :posts, id: :uuid do |t| t.references :author, type: :uuid, foreign_key: true # Other columns... t.timestamps end
在此範例中,posts 表格中的
author_id
資料行參照 authors 表格的id
資料行。透過明確將類型設定為:uuid
,你可以確保外鍵資料行與其參照的主索引鍵的資料類型相符。針對其他關聯和資料庫,相應地調整語法。遷移變更
當為你的模型產生遷移時,你會注意到它將 id 指定為
uuid:
類型$ bin/rails g migration CreateAuthors
class CreateAuthors < ActiveRecord::Migration[8.0] def change create_table :authors, id: :uuid do |t| t.timestamps end end end
這會產生下列綱要
create_table "authors", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end
在此遷移中,
id
資料行定義為 UUID 主索引鍵,其預設值由gen_random_uuid()
函數產生。
UUID 保證在不同系統之間是全域唯一的,使其適用於分散式架構。它們還透過提供不依賴集中式 ID 產生的唯一識別碼來簡化與外部系統或 API 的整合,並且與自動遞增整數不同,UUID 不會公開表格中記錄總數的相關資訊,這對安全性可能是有益的。
然而,UUID 也可能因為它們的大小而影響效能,並且更難以建立索引。與整數主索引鍵和外鍵相比,UUID 在寫入和讀取方面的效能會較差。
因此,在決定使用 UUID 作為主索引鍵之前,評估權衡取捨並考慮應用程式的特定需求至關重要。
10.2 資料遷移
資料遷移涉及在資料庫中轉換或移動資料。在 Rails 中,通常不建議使用遷移檔案執行資料遷移。原因如下
- 關注點分離:綱要變更和資料變更具有不同的生命週期和目的。綱要變更會變更資料庫的結構,而資料變更會變更內容。
- 回滾複雜性:資料遷移可能難以安全且可預測地回滾。
- 效能:資料遷移可能需要很長時間才能執行,並且可能會鎖定你的表格,影響應用程式效能和可用性。
請改為考慮使用 maintenance_tasks
gem。此 gem 提供了一個框架,用於建立和管理資料遷移和其他維護任務,其方式安全且易於管理,而不會干擾綱要遷移。