1 遷移概觀
遷移是一種方便的方式,可讓您隨著時間推移一致地變更資料庫 Schema。它們使用 Ruby DSL,讓您不必手動撰寫 SQL,並讓您的 Schema 和變更與資料庫無關。
您可以將每個遷移視為資料庫的「新版本」。Schema 最初沒有任何內容,而每個遷移都會修改 Schema 以新增或移除資料表、欄位或項目。Active Record 知道如何沿著這條時間軸更新您的 Schema,將其從歷史中的任何時間點更新到最新版本。Active Record 也會更新您的 db/schema.rb
檔案,以符合資料庫的最新結構。
以下是遷移範例
class CreateProducts < ActiveRecord::Migration[7.1]
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 會自動管理它們。
請注意,我們定義了我們希望隨著時間推移而發生的變更。在執行此遷移之前,不會有表格。執行後,表格將存在。Active Record 也知道如何還原此遷移:如果我們回滾此遷移,它將移除表格。
在支援使用變更架構的陳述式進行交易的資料庫上,每個遷移都會包裝在交易中。如果資料庫不支援這一點,則當遷移失敗時,已成功執行的部分不會被回滾。您必須手動回滾所做的變更。
有些查詢無法在交易中執行。如果您的轉接器支援 DDL 交易,您可以使用 disable_ddl_transaction!
為單一遷移停用它們。
1.1 使不可逆轉的變為可能
如果您希望遷移執行 Active Record 不知道如何還原的動作,您可以使用 reversible
class ChangeProductsPrice < ActiveRecord::Migration[7.1]
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[7.1]
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
稍後會詳細說明 reversible
。
2 產生遷移
2.1 建立獨立遷移
遷移儲存在 db/migrate
目錄中的檔案中,每個遷移類別一個檔案。檔案名稱的格式為 YYYYMMDDHHMMSS_create_products.rb
,也就是說一個 UTC 時間戳記識別遷移,後接底線,再接遷移的名稱。遷移類別的名稱(使用駝峰式大小寫)應與檔案名稱的後段相符。例如,20080906120000_create_products.rb
應定義類別 CreateProducts
,而 20080906120001_add_details_to_products.rb
應定義 AddDetailsToProducts
。Rails 使用此時間戳記來確定應執行哪個遷移以及以何種順序執行,因此,如果您從另一個應用程式複製遷移或自己產生檔案,請注意其在順序中的位置。
當然,計算時間戳記一點都不好玩,因此 Active Record 提供一個產生器來處理為您製作時間戳記的工作
$ bin/rails generate migration AddPartNumberToProducts
這將建立一個適當命名的空遷移
class AddPartNumberToProducts < ActiveRecord::Migration[7.1]
def change
end
end
這個產生器可以做的遠遠不只是在檔案名稱之前加上時間戳記。根據命名慣例和額外的(可選)參數,它還可以開始充實遷移。
2.2 新增欄位
如果遷移名稱的格式為「AddColumnToTable」或「RemoveColumnFromTable」,並且後面接一串欄位名稱和類型,那麼將建立一個包含適當的 add_column
和 remove_column
陳述式的遷移。
$ bin/rails generate migration AddPartNumberToProducts part_number:string
這將產生以下遷移
class AddPartNumberToProducts < ActiveRecord::Migration[7.1]
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[7.1]
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[7.1]
def change
add_column :products, :part_number, :string
add_column :products, :price, :decimal
end
end
2.3 移除欄位
類似地,您可以產生一個遷移,從命令列中移除一個欄位
$ bin/rails generate migration RemovePartNumberFromProducts part_number:string
這會產生適當的 remove_column
陳述
class RemovePartNumberFromProducts < ActiveRecord::Migration[7.1]
def change
remove_column :products, :part_number, :string
end
end
2.4 建立新表格
如果遷移名稱是「CreateXXX」格式,並且後面接著一個欄位名稱和類型的清單,那麼會產生一個遷移,建立一個表格 XXX,其中包含所列出的欄位。例如
$ bin/rails generate migration CreateProducts name:string part_number:string
會產生
class CreateProducts < ActiveRecord::Migration[7.1]
def change
create_table :products do |t|
t.string :name
t.string :part_number
t.timestamps
end
end
end
一如往常,為您產生的內容只是一個起點。您可以透過編輯 db/migrate/YYYYMMDDHHMMSS_add_details_to_products.rb
檔案,隨意新增或移除內容。
2.5 使用參照建立關聯
此外,產生器接受的欄位類型為 references
(也可用作 belongs_to
)。例如,
$ bin/rails generate migration AddUserRefToProducts user:references
會產生以下 add_reference
呼叫
class AddUserRefToProducts < ActiveRecord::Migration[7.1]
def change
add_reference :products, :user, foreign_key: true
end
end
此遷移將建立一個 user_id
欄位。參照 是建立欄位、索引、外來鍵,甚至多型關聯欄位的簡寫。
還有一個產生器,如果名稱中包含 JoinTable
,它將產生關聯表格
$ bin/rails generate migration CreateJoinTableCustomerProduct customer product
將產生以下遷移
class CreateJoinTableCustomerProduct < ActiveRecord::Migration[7.1]
def change
create_join_table :customers, :products do |t|
# t.index [:customer_id, :product_id]
# t.index [:product_id, :customer_id]
end
end
end
2.6 模型產生器
模型、資源和架構產生器會建立適當的遷移,以新增新的模型。此遷移將已包含建立相關資料表的指示。如果你告訴 Rails 你要哪些欄位,則也會建立新增這些欄位的陳述式。例如,執行
$ bin/rails generate model Product name:string description:text
這將建立一個看起來像這樣的遷移
class CreateProducts < ActiveRecord::Migration[7.1]
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[7.1]
def change
add_column :products, :price, :decimal, precision: 5, scale: 2
add_reference :products, :supplier, polymorphic: true
end
end
請查看產生器的說明輸出 (bin/rails generate --help
) 以取得進一步的詳細資料。
3 編寫遷移
一旦你使用其中一個產生器建立了遷移,就是開始工作的時間了!
3.1 建立資料表
create_table
方法是最基本的其中一個方法,但大多數時候,它會從使用模型、資源或架構產生器中為你產生。典型的用法會是
create_table :products do |t|
t.string :name
end
此方法會建立一個具有稱為 name
的欄位的 products
資料表。
預設情況下,create_table
會隱含地為你建立一個稱為 id
的主鍵。你可以使用 :primary_key
選項變更欄位名稱,或將陣列傳遞給 :primary_key
以取得複合主鍵。如果你根本不想要主鍵,則可以傳遞選項 id: false
。
如果你需要傳遞資料庫特定的選項,則可以在 :options
選項中放置 SQL 片段。例如
create_table :products, options: "ENGINE=BLACKHOLE" do |t|
t.string :name, null: false
end
這會將 ENGINE=BLACKHOLE
附加到用於建立資料表的 SQL 陳述式。
可以在 create_table
區塊中建立的欄位上建立索引,方法是傳遞 index: true
或選項雜湊給 :index
選項
create_table :users do |t|
t.string :name, index: true
t.string :email, index: { unique: true, name: 'unique_emails' }
end
此外,您可以傳遞 :comment
選項,其中包含任何說明,這些說明將儲存在資料庫本身中,並且可以使用資料庫管理工具(例如 MySQL Workbench 或 PgAdmin III)檢視。強烈建議在大型資料庫的應用程式中指定遷移中的註解,因為它可以幫助人們了解資料模型並產生文件。目前僅 MySQL 和 PostgreSQL 適配器支援註解。
3.2 建立關聯表格
遷移方法 create_join_table
建立 HABTM(具有並屬於多個)關聯表格。典型的用法如下
create_join_table :products, :categories
此遷移將建立一個 categories_products
表格,其中包含兩個稱為 category_id
和 product_id
的欄位。
這些欄位預設將選項 :null
設為 false
,表示您必須提供一個值才能將記錄儲存到此表格中。這可以透過指定 :column_options
選項來覆寫
create_join_table :products, :categories, column_options: { null: true }
預設情況下,關聯表格的名稱來自傳遞給 create_join_table 的前兩個參數的聯集,並按字母順序排列。
若要自訂表格的名稱,請提供一個 :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_null :products, :name, false
change_column_default :products, :approved, from: true, to: false
這會將產品上的 :name
欄位設定為 NOT NULL
欄位,並將 :approved
欄位的預設值從 true 變更為 false。這兩個變更只會套用於未來的交易,任何現有的記錄都不會套用。
當將 null 約束設定為 true 時,表示欄位將接受 null 值,否則會套用 NOT NULL
約束,且必須傳遞值才能將記錄保留到資料庫。
您也可以將上述 change_column_default
遷移寫成 change_column_default :products, :approved, false
,但與前一個範例不同,這會讓您的遷移不可逆。
3.5 欄位修改器
建立或變更欄位時可以套用欄位修改器
comment
為欄位新增註解。collation
指定string
或text
欄位的對照。default
允許在欄位上設定預設值。請注意,如果您使用動態值(例如日期),預設值僅會在第一次計算(即套用遷移的日期)。對NULL
使用nil
。limit
設定string
欄位的最大字元數,以及text/binary/integer
欄位的最大位元組數。null
允許或不允許欄位中的NULL
值。precision
指定decimal/numeric/datetime/time
欄位的精度。scale
指定decimal
和numeric
欄位的比例,表示小數點後位數。
對於 add_column
或 change_column
,沒有選項可以新增索引。它們需要使用 add_index
分別新增。
某些轉接器可能會支援其他選項;請參閱特定轉接器的 API 文件以取得更多資訊。
在產生遷移時,無法透過命令列指定 null
和 default
。
3.6 參考
add_reference
方法允許建立適當命名的欄位,作為一個或多個關聯之間的連接。
add_reference :users, :role
此遷移將在使用者表格中建立 role_id
欄位。它也會為此欄位建立索引,除非明確使用 index: false
選項表示不要建立。
另請參閱 Active Record 關聯 指南以深入了解。
add_belongs_to
方法是 add_reference
的別名。
add_belongs_to :taggings, :taggable, polymorphic: true
多型選項將在標籤表格上建立兩個欄位,可用於多型關聯: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
相符。
如果from_table
欄位名稱無法從to_table
名稱推導出來,您可以使用: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[7.1]
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 當 Helper 不夠用時
如果 Active Record 提供的 Helper 不夠用,你可以使用 execute
方法來執行任意的 SQL
Product.connection.execute("UPDATE products SET price = 'free' WHERE 1=1")
有關個別方法的更多詳細資訊和範例,請查看 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 ExampleMigration < ActiveRecord::Migration[7.1]
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
將確保指示也按正確順序執行。如果還原前一個範例遷移,則 down
區塊將在移除 users.address
欄位之後、移除 distributors
資料表之前執行。
3.12 使用 up
/down
方法
您也可以使用舊式遷移,使用 up
和 down
方法,而不是 change
方法。
up
方法應描述您要對架構進行的轉換,而遷移的 down
方法應還原 up
方法所做的轉換。換句話說,如果您執行 up
後再執行 down
,資料庫架構應保持不變。
例如,如果您在 up
方法中建立一個資料表,您應在 down
方法中將其刪除。明智的做法是按照 up
方法中建立的順序完全相反的順序執行轉換。reversible
區段中的範例等同於
class ExampleMigration < ActiveRecord::Migration[7.1]
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
。
如果有人嘗試還原您的遷移,系統將顯示一條錯誤訊息,指出無法還原。
3.14 回復先前的遷移
你可以使用 Active Record 的能力,使用 revert
方法回滾遷移
require_relative "20121212123456_example_migration"
class FixupExampleMigration < ActiveRecord::Migration[7.1]
def change
revert ExampleMigration
create_table(:apples) do |t|
t.string :variety
end
end
end
revert
方法也接受一個要反轉的指令區塊。這對於反轉先前遷移的選定部分可能很有用。
例如,讓我們假設 ExampleMigration
已提交,並且稍後決定不再需要 Distributors 檢視。
class DontUseDistributorsViewMigration < ActiveRecord::Migration[7.1]
def change
revert do
# copy-pasted code from ExampleMigration
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
的順序。 - 用
drop_table
取代create_table
。 - 最後,用
down
取代up
,反之亦然。
這一切都由 revert
處理。
4 執行遷移
Rails 提供一組指令,用於執行某些遷移集。
你將會使用的第一個與遷移相關的 rails 指令可能是 bin/rails db:migrate
。在最基本的型式中,它只會執行尚未執行的所有遷移的 change
或 up
方法。如果沒有這樣的遷移,它就會退出。它會根據遷移的日期依序執行這些遷移。
請注意,執行 db:migrate
指令也會呼叫 db:schema:dump
指令,這將更新你的 db/schema.rb
檔案,以符合你的資料庫結構。
如果你指定目標版本,Active Record 將執行必要的遷移(變更、上行、下行),直到達到指定的版本。版本是遷移檔案名稱上的數字前綴。例如,要遷移到版本 20080906120000,請執行
$ bin/rails db:migrate VERSION=20080906120000
如果版本 20080906120000 大於目前版本(即向上遷移),這將在所有遷移中執行 change
(或 up
)方法,包括 20080906120000,並且不會執行任何後續遷移。如果向下遷移,這將在所有遷移中執行 down
方法,包括 20080906120000,但不包含 20080906120000。
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
這些 rails 命令都不會執行您無法使用 db:migrate
執行的任何操作。它們的存在是為了方便,因為您不需要明確指定要遷移到的版本。
4.2 設定資料庫
bin/rails db:setup
命令將建立資料庫、載入架構並使用種子資料初始化它。
4.3 準備資料庫
bin/rails db:prepare
命令類似於 bin/rails db:setup
,但它的運作具有冪等性。
- 如果尚未建立資料庫,命令將像
bin/rails db:setup
一樣執行。 - 如果資料庫存在但尚未建立資料表,命令將載入架構、執行任何待處理的遷移、傾印更新的架構,最後載入種子資料。
- 如果資料庫和資料表都存在,但尚未載入種子資料,命令將只載入種子資料。
- 如果資料庫、資料表和種子資料都已到位,此指令將不會執行任何動作。
一旦資料庫、資料表和種子資料都已建立,即使先前載入的種子資料或現有的種子檔案已被變更或刪除,此指令也不會嘗試重新載入種子資料。若要重新載入種子資料,您可以手動執行 bin/rails db:seed
。
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=20080906120000
執行此指令後,版本為「20080906120000」的遷移將執行 change
方法(或 up
方法)。
首先,此指令會檢查遷移是否存在,以及是否已執行,如果已執行,則不會執行任何動作。
如果指定的版本不存在,Rails 將會擲回例外。
$ bin/rails db:migrate VERSION=zomg
rails aborted!
ActiveRecord::UnknownMigrationVersionError:
No migration with version number zomg.
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[7.1]
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
會抑制所有輸出。
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.active_record.schema_format
設定所控制,此設定定義於 config/application.rb
。預設格式為 :ruby
,或者也可以設定為 :sql
。
6.2.1 使用預設 :ruby
架構
當選取 :ruby
時,架構會儲存在 db/schema.rb
。如果你查看這個檔案,你會發現它看起來很像一個非常大的遷移
ActiveRecord::Schema[7.1].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。
8 遷移和種子資料
Rails 遷移功能的主要目的是使用一致的程序發出修改架構的命令。遷移也可用于新增或修改資料。這在無法刪除和重新建立的現有資料庫(例如生產資料庫)中很有用。
class AddInitialProducts < ActiveRecord::Migration[7.1]
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 資料庫表格中保留對特定於它們的遷移時間戳的參考。此表格用於追蹤遷移是否已在特定環境中執行。
如果你執行 bin/rails db:migrate:status
指令,它會顯示每個遷移的狀態(已執行或未執行),你應該會看到 ********** NO FILE **********
顯示在曾經在特定環境中執行,但現在在 db/migrate/
目錄中找不到的任何已刪除遷移檔案旁邊。
9.1 來自引擎的遷移
不過,對於 引擎 有一個但書。安裝來自引擎的遷移的 Rake 任務是冪等的,這表示不論呼叫它們多少次,它們都會有相同的結果。由於先前的安裝,出現在父應用程式的遷移會被略過,而遺失的遷移會被複製並加上新的領先時間戳。如果你刪除舊的引擎遷移並再次執行安裝任務,你會取得具有新時間戳的新檔案,而 db:migrate
會嘗試再次執行它們。
因此,你通常會想要保留來自引擎的遷移。它們有如下所示的特殊註解
# This migration comes from blorgh (originally 20210621082949)
回饋
我們鼓勵你協助提升本指南的品質。
如果你看到任何錯字或事實錯誤,請協助貢獻。若要開始,你可以閱讀我們的 文件貢獻 部分。
您也可能會發現不完整或過時的內容。請務必為 main 新增任何遺失的說明文件。請務必先查看 Edge Guides,以驗證問題是否已在主分支中修復。查看 Ruby on Rails Guides Guidelines 以了解樣式和慣例。
如果您發現需要修復但無法自行修補的任何原因,請 開啟問題。
最後但並非最不重要的一點是,歡迎在 官方 Ruby on Rails 論壇 上討論與 Ruby on Rails 說明文件相關的任何事項。