v7.1.3.2
更多資訊請至 rubyonrails.org: 更多 Ruby on Rails

Active Record 遷移

遷移是 Active Record 的一項功能,可讓您隨著時間推移演變資料庫架構。遷移允許您使用 Ruby DSL 來描述對資料表的變更,而不是以純 SQL 編寫架構修改。

閱讀本指南後,您將了解

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_atupdated_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.updirection.down 的區塊。

或者,您可以使用 updown 代替 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_columnremove_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_columnadd_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_idproduct_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

此遷移將移除 descriptionname 欄位,建立一個名為 part_number 的新字串欄位,並為其新增索引。最後,它將 upccode 欄位重新命名為 upc_code

3.4 變更欄位

類似於我們先前介紹的 remove_columnadd_column 方法 在此,Rails 也提供 change_column 遷移方法。

change_column :products, :part_number, :text

這會將產品資料表上的 part_number 欄位變更為 :text 欄位。

change_column 指令是不可逆的。您應該提供自己的 reversible 遷移,就像我們 之前討論的那樣。

除了 change_column 之外,change_column_nullchange_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 指定 stringtext 欄位的對照。
  • default 允許在欄位上設定預設值。請注意,如果您使用動態值(例如日期),預設值僅會在第一次計算(即套用遷移的日期)。對 NULL 使用 nil
  • limit 設定 string 欄位的最大字元數,以及 text/binary/integer 欄位的最大位元組數。
  • null 允許或不允許欄位中的 NULL 值。
  • precision 指定 decimal/numeric/datetime/time 欄位的精度。
  • scale 指定 decimalnumeric 欄位的比例,表示小數點後位數。

對於 add_columnchange_column,沒有選項可以新增索引。它們需要使用 add_index 分別新增。

某些轉接器可能會支援其他選項;請參閱特定轉接器的 API 文件以取得更多資訊。

在產生遷移時,無法透過命令列指定 nulldefault

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_typetaggable_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支援其他幾個選項,例如nameon_deleteif_not_existsvalidatedeferrable

外來鍵也可以使用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只支援單一欄位外來鍵。executestructure.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 的文件,它提供了 changeupdown 方法中可用的方法。

有關 create_table 所產生的物件的可用方法,請參閱 ActiveRecord::ConnectionAdapters::TableDefinition

而對於 change_table 所產生的物件,請參閱 ActiveRecord::ConnectionAdapters::Table

3.10 使用 change 方法

change 方法是撰寫遷移的主要方式。它適用於大多數 Active Record 知道如何自動還原遷移動作的情況。以下是 change 支援的一些動作

change_table 也是可逆的,只要區塊僅呼叫可逆運算,例如上面列出的運算。

如果您需要使用任何其他方法,您應該使用 reversible 或撰寫 updown 方法,而不是使用 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 方法

您也可以使用舊式遷移,使用 updown 方法,而不是 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 的情況下撰寫,但這將涉及更多步驟

  1. 反轉 create_tablereversible 的順序。
  2. drop_table 取代 create_table
  3. 最後,用 down 取代 up,反之亦然。

這一切都由 revert 處理。

4 執行遷移

Rails 提供一組指令,用於執行某些遷移集。

你將會使用的第一個與遷移相關的 rails 指令可能是 bin/rails db:migrate。在最基本的型式中,它只會執行尚未執行的所有遷移的 changeup 方法。如果沒有這樣的遷移,它就會退出。它會根據遷移的日期依序執行這些遷移。

請注意,執行 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.rbdb/structure.sql 檔案的內容。如果無法回滾遷移,bin/rails db:reset 可能無法為您提供協助。若要進一步了解如何傾印架構,請參閱 架構傾印與您 部分。

4.5 執行特定遷移

如果您需要執行特定遷移向上或向下,db:migrate:updb:migrate:down 指令將會執行。只需指定適當的版本,對應的遷移就會呼叫其 changeupdown 方法,例如

$ 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_tableadd_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.rbdb/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 說明文件相關的任何事項。