More at rubyonrails.org: Overview | Download | Deploy | Code | Screencasts | Documentation | Ecosystem | Community | Blog

Migrations(資料庫遷移)

Migrations(資料庫遷移)可以很方便的讓你有條理地修改資料庫。雖然說要修改資料庫,手動編輯 SQL 也可以辦到,但是這樣你得通知其他開發者去執行一樣的步驟。另外,你也得一直注意下次佈署的時候,在正式上線的伺服器(production machine)上面追蹤並執行這些變更。

Active Record 會自動追蹤哪些 migrations 已經執行過、哪些還沒。所以,您只要更新手頭的原始碼,然後執行 rake db:migrate,其餘的就交給 Active Record,它會自己搞懂該跑哪些 migrations。還有,它也會更新 db/schema.rb 檔案,以符合改動後的資料庫結構。

有了 migrations,就可以用 Ruby 來寫資料庫變更。一件很棒的事情是: migration 是獨立於資料庫系統的(和大多數 Active Record 的功能一樣),也就是說,您不用去煩惱各種資料庫系統的語法差異,像是 SELECT * 的各種變化形之類的。(當然啦,如果要針對某個特定資料庫系統,撰寫專屬功能的話,您也可以直接寫原始 SQL)。打個比方,您可以在開發階段使用 SQLite3,在正式上線階段則使用 MySQL,兩者間的語法細節,它會自動幫你搞定。

接下來,您將學會 migrations 的一切,包括:

1 Migration 構造解剖

深入 migration 的細節之前,先來看看它可以用來做哪些事情。這邊有幾個例子:

class CreateProducts < ActiveRecord::Migration def self.up create_table :products do |t| t.string :name t.text :description t.timestamps end end def self.down drop_table :products end end

這個 migration 新增了一個資料表 products,包含一個字串欄 name 以及一個文字欄 description。另外,主鍵欄 id 其實也會加進去,這是預設就會做的動作,所以我們不用特別寫在 migrations 裡面。除了主鍵之外,還有兩個 timestamp 欄 created_at 以及 updated_at,也會透過 t.timestamps 加進去。這個 migration 的復原動作很簡單,那就是刪掉這個資料表。

Migrations 用途多多,除了改變 schema(資料庫綱要)之外,還可以修復不正確的資料,或者建立新的欄位(fields):

class AddReceiveNewsletterToUsers < ActiveRecord::Migration def self.up change_table :users do |t| t.boolean :receive_newsletter, :default => false end User.update_all ["receive_newsletter = ?", true] end def self.down remove_column :users, :receive_newsletter end end

這個 migration 在資料表 users 中,新增一個欄位 receive_newsletter。對於新註冊的使用者,我們要它預設成 false。而對於已經註冊的使用者,我們則認定他要選擇收信,所以用 User 這個 Model 來將資料全部更新成 true

在 migrations 中使用 Models 的一些 警告

1.1 Migrations 是類別(classes)

每個 migration 都是 ActiveRecord::Migration 的子類別(subclass),有兩個類別方法可以用: up(執行遷移)以及 down(回復遷移)。

Active Record 提供以下獨立於資料庫系統的方法,來執行一般常見的資料表定義任務:

  • create_table
  • change_table
  • drop_table
  • add_column
  • change_column
  • rename_column
  • remove_column
  • add_index
  • remove_index

如果,要做某些資料庫系統特有的任務(例如,產生一個 foreign key 約束),可以用 execute 來執行任意的 SQL。說穿了,migration 不過是個普通的 Ruby 類別而已,所以,您可以在 migration 裡面做的事情,並不限於這些方法。舉個例子,新增一欄位後,您可以自己寫程式用現有的資料去設定這一欄的值(必要的話,使用您的 Models)。

某些資料庫,像是 PostgreSQL 或 SQLite3,它的 transactions(交易功能)包括了變更 schema(資料庫綱要)陳述句(statements)。使用這類資料庫系統的時候,migrations 就會被打包在 transaction 之內。相反的,如果您的資料庫系統,無法把 migration 包在 transaction 之內的話(如 MySQL),那麼,當 migration 失敗了,改到一半的 schema,並不會隨著交易的取消,而自動滾回(roll back)。您必須手動挑出這些已經更動的部分。

1.2 關於名稱

Migrations 存放的地方,是在 db/migrate 裡面,每個 migration 類別各是一個檔案。檔案名稱的格式是 YYYYMMDDHHMMSS_create_products.rb,檔名最前面,是一個用來識別 migration 的 UTC timestamp(時間戳章),然後接一個底線,然後接 migration 名稱。Migration class 名稱是採用 CamelCased (駝峰式)的命名格式,這個名稱,跟檔名的後半段應該是一樣的。所以,20080906120000_create_products.rb 裡面,就該定義 CreateProducts,而 20080906120001_add_details_to_products.rb 裡面,則定義 AddDetailsToProducts。若要改檔名,就必須更新檔案中的 class,否則 Rails 會回報為遺失 class。

對 Rails 來說,辨識 migration 的時候,只會用到編號(也就是 timestamp)的部份。Rails 2.1 之前的版本,migration 從 1 開始編號,每次產生新的 migration 就加一號。這種方法,在多人開發的時候,就很容易編號衝突,一旦撞號了,就得將 migration 滾回(rollback)並且重新編號。若您說什麼都硬是要回到這種舊式編號結構,可以在 config/application.rb 中加入下面這幾行:

config.active_record.timestamped_migrations = false

透過時間戳章(timestamps)以及記錄已經執行過哪些 migrations 的特性,讓 Rails 能夠應付多人開發的狀況。

舉例來說:艾莉絲先新增 2008090612000020080906123000,鮑伯才接著新增 20080906124500 並且執行了它。艾莉絲寫好了,還在檢查她的東西,此時鮑伯卻把剛才的變更給砍了。鮑伯自砍後,艾莉絲才下了她的 rake db:migrate 指令。在這個混亂的時刻,聰明的 Rails 知道,只有艾莉絲的兩項 migrations 還沒做過,所以 rake db:migrate 只會執行這兩項,雖然鮑伯的 migration 擁有較新 timestamp,但 Rails 不會執行它。同樣的,鮑伯先前下的回溯(migrating down)命令,也不會去執行艾莉絲這兩項的 down 方法。

當然囉,團隊內溝通不可少,假設,艾莉絲的 migration 移除了鮑伯的 migration 會用到的資料表,那鐵定會出包的。

1.3 變更 Migrations

偶爾,寫 migration 會寫錯。若很不幸的已經執行了,那麼,不能單純的只是把它改一改再執行一次,因為 Rails 認為它已經跑過了,所以執行 rake db:migrate 時 Rails 會啥都不做。所以說,您必須先將 migration 滾回,滾回可以用 rake db:rollback 指令。滾回後再重新編輯,執行 rake db:migrate 跑正確的版本。

一般來說,最好別編輯現有的 migrations,以免害到同事也害到自己。而且,若是在正式上線的伺服器(production machines)那就更慘了。您應該寫一個新的 migration 來做資料庫變更。如果這個 migration 還沒進入版本控制系統(也就是說,這些變更還沒有散佈出去),那麼直接編輯是比較無害的。這些是基本常識。

2 建立一個 Migration

2.1 建立一個 Model

Model 和 scaffold 的 generators(產生器)在新增 Model 時都會自動產生搭配的 migration。在這個 migration 裡已經把建立資料表的步驟都寫好了。如果執行時就告訴 Rails 需要哪些欄位,Rails 還會連新增欄位的程式一起寫好。例如:

rails generate model Product name:string description:text

就會建立一個 migration 長得像這樣:

class CreateProducts < ActiveRecord::Migration def self.up create_table :products do |t| t.string :name t.text :description t.timestamps end end def self.down drop_table :products end end

您可以追加更多的 欄位名稱/欄位型態,愛加多少就加多少。如同前面提過的, t.timestamps 欄位(這會建立 updated_atcreated_at 這兩個欄位,而 Active Record 會在資料新增和更新時自動更新時間)也會自動加上去。

2.2 建立一個獨立的 Migration

若您建立 migration,不是為了新增 Model,而是為了其他目的(比方說,在現有資料表中新增一個欄位),那可以只用 migration generator(資料庫遷移產生器)來做:

rails generate migration AddPartNumberToProducts

這樣會建立一個空的、但已命名好的 migration:

class AddPartNumberToProducts < ActiveRecord::Migration def self.up end def self.down end end

把 migration 的名稱,取做「AddXXXToYYY」、「RemoveXXXFromYYY」這類的格式,後面再接上一串欄名/欄類型的清單,這樣的話,這個 migration 就會含有對應的 add_columnremove_column 敘述。

rails generate migration AddPartNumberToProducts part_number:string

會產生出:

class AddPartNumberToProducts < ActiveRecord::Migration def self.up add_column :products, :part_number, :string end def self.down remove_column :products, :part_number end end

同理,

rails generate migration RemovePartNumberFromProducts part_number:string

則產生出:

class RemovePartNumberFromProducts < ActiveRecord::Migration def self.up remove_column :products, :part_number end def self.down add_column :products, :part_number, :string end end

這個技巧,可以產生不只一欄,像是:

rails generate migration AddDetailsToProducts part_number:string price:decimal

就可以產生出:

class AddDetailsToProducts < ActiveRecord::Migration def self.up add_column :products, :part_number, :string add_column :products, :price, :decimal end def self.down remove_column :products, :price remove_column :products, :part_number end end

這些自動產生的東西,只是個起點,您可以增刪修改,直到滿意為止。

3 撰寫 Migration

用 generator 產生出 migration 後,接下來就是我們的工作了。

3.1 建立資料表

要建立新資料表的話,可以用 migration 的 create_table 方法。典型的用法是:

create_table :products do |t| t.string :name end

這會建立一個 products 資料表,裡面有個欄叫做 name(當然,如同先前所說的,也會順便加上一個主鍵欄 id)。

這段程式碼區塊(block)讓您可以在資料表上新增欄位。新增的方法有二,第一種格式,也就是傳統的寫法,看起來像這樣:

create_table :products do |t| t.column :name, :string, :null => false end

第二種格式,也就是所謂「sexy」寫法,把 column 給扔了,取而代之,用 stringinteger 等方法,來建立該類型的欄。至於接在後面的參數則是一樣。

create_table :products do |t| t.string :name, :null => false end

create_table 時新增的主鍵欄,預設名稱是 id,要改名稱,可以加上 :primary_key 這個設定,記得,改名稱的同時,別忘了要同時更新相對應的 Model 程式。如果,您根本就不要主鍵(例如,使用多對多繫結 (HABTM) 的資料表時),那就傳入 :id => false。另外,如果要傳入某個特定資料庫系統的設定,您可以在 :options 選項中放一個 SQL 片段。舉例:

create_table :products, :options => "ENGINE=BLACKHOLE" do |t| t.string :name, :null => false end

這樣就會在建立資料表的 SQL 中,加上 ENGINE=BLACKHOLE。(如果是用 MySQL 的話,預設是 ENGINE=InnoDB

Active Record 有支援的型態(types)包括::primary_key:string:text:integer:float:decimal:datetime:timestamp:time:date:binary:boolean

這些類型,會自動對應到下層資料庫,例如,在 MySQL 底下,:string 會對應到 VARCHAR(255)。如果,您要建立 Active Record 不支援的類型,那可以用「non-sexy」語法,比方說:

create_table :products do |t| t.column :name, 'polygon', :null => false end

不過,這樣就失去了不同資料庫系統之間的可攜性。

3.2 變更資料表

要變更現有的資料表,可以用create_table 的近親 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,且在此欄設定了索引(index)。最後,重新命名了 upccode 欄。這跟下面這段的結果是一樣的:

remove_column :products, :description remove_column :products, :name add_column :products, :part_number, :string add_index :products, :part_number rename_column :products, :upccode, :upc_code

對照之下,可以發現,用程式碼區塊(block)的寫法,就不用重複打資料表名稱,因為,它會將修改同個資料表的所有陳述句群組起來。除此之外,寫在區塊內的各別動作,也可以縮短,像是 remove_column 變成 remove,而 add_index 只要寫 index

3.3 特殊方法

有些功能很常用,像是新增 created_atupdated_at 欄。為此,Active Record 提供了捷徑:

create_table :products do |t| t.timestamps end

以上會建立一個新的 products 資料表,內含這兩個欄(還有 id 欄)。此外:

change_table :products do |t| t.timestamps end

則是會在現有的資料表中,加入這兩欄。

另一個特殊方法,叫做 references,把它寫成 belongs_to 也可以。它的功能,最基本的話是增進易讀性:

create_table :products do |t| t.references :category end

以上會建立一個欄 category_id,並給它一個恰當的類型。這邊要注意,您所輸入的,是 Model 的名稱,而不是欄位的名稱。 Active Record 會自動在 Model 名稱的尾端加上 _id。若您有 polymorphic(多形)的 belongs_to 關係,那麼 references 會把兩個所需的欄都加進去:

create_table :products do |t| t.references :attachment, :polymorphic => {:default => 'Photo'} end

會新增一個 attachment_id 欄位,以及一個預設值為 Photo 的字串欄位 attachment_type

references 輔助工具不會建立外部鍵約束(foreign key constraints)。您必須使用 execute 來做,或者用能夠加入 外部鍵支援(foreign key support) 的外掛。

如果 Active Record 的特殊方法不夠用,您可以用 execute 功能來執行任意的 SQL。

對於各方法的細節跟範例,請參考 API 文件,特別是關於 ActiveRecord::ConnectionAdapters::SchemaStatements (提供了在 updown 裡面,可使用的方法)、 ActiveRecord::ConnectionAdapters::TableDefinition (提供了在 create_table 所產生的物件裡面,可使用的方法)、以及 ActiveRecord::ConnectionAdapters::Table (提供了在 change_table 所產生的物件裡面,可使用的方法)。

3.4 撰寫您的 down 方法

Migraiton 裡面的 down 方法,要能復原 up 方法所造成的變更。也就是說,若執行了 up 然後執行 down,那麼 database schema(資料庫綱要)應該會原封不動。所以說,如果用 up 建立一個資料表,就該在 down 中刪除它。明智的作法是,使用跟 up 完全相反的順序,來做這些事情。例如:

class ExampleMigration < ActiveRecord::Migration def self.up create_table :products do |t| t.references :category end #add a foreign key execute <<-SQL ALTER TABLE products ADD CONSTRAINT fk_products_categories FOREIGN KEY (category_id) REFERENCES categories(id) SQL add_column :users, :home_page_url, :string rename_column :users, :email, :email_address end def self.down rename_column :users, :email_address, :email remove_column :users, :home_page_url execute "ALTER TABLE products DROP FOREIGN KEY fk_products_categories" drop_table :products end end

有時候,您的 migrations 會做出無法復原的事情,像是刪掉某些資料之類的。如果出現了這種無法倒回 migration 的狀況,您可以在 down 方法中丟出例外(raise) IrreversibleMigration。這樣一來,如果有人想復原您的 migration,會出現錯誤訊息,顯示它無法執行。

4 執行 Migrations

Rails 提供一系列 rake 任務來執行 migrations。第一個跟 migration 有關的 rake 任務是 db:migrate。它的用法,最基本的,就是單純的執行所有還沒執行 migraitons 的 up 方法。若所有 migrations 都執行過了,它就會直接結束。

要注意的是,執行 db:migrate 也會一起執行 db:schema:dump,去更新 db/schema.rb 檔,以便跟資料庫的結構相吻合。

如果你要 migrate 到某個特定版本,Active Record 會執行所需的 migrations(可能是 up 或 down),直到到達那個版本為止。所謂版本,就是 migrations 檔名的前置數字,例如要遷移到版本 20080906120000 的話,就執行:

rake db:migrate VERSION=20080906120000

如果數字比當前版本還大(也就是說,往上遷移),那就會執行 up 方法到包含 20080906120000 在內的所有版本,如果是往下遷移則會執行所有 down 方法,但不包括 20080906120000 本身。

4.1 滾回(Rolling Back)

另一個常見的任務是滾回最後一個 migration,比如說不小心打錯了要修正。要下滾回指令的時候,可以不用輸入落落長的版本編號,而可以這樣就好:

rake db:rollback

這會執行最後一個 migration 的 down 方法。如果要復原數個 migrations 的話,可以多給一個 STEP 參數:

rake db:rollback STEP=3

這樣,就會執行最後 3 個 migrations 的 down 方法。

要滾回然後重新執行 migration 的話,db:migrate:redo 是個捷徑。如果要倒退不只一個版本,可以用 STEP 參數,就跟 db:rollback 的用法一樣:

rake db:migrate:redo STEP=3

這兩個 Rake 任務,只是比較方便,讓您不用輸入一大串版本編號的數字。除了輸入比較便利之外,並沒有比 db:migrate 多出什麼額外的功能。

最後是 db:reset 任務,它會刪掉(drop)資料庫、然後重新建立資料庫,然後在重新建立的資料庫裡面,載入目前的 schema(資料庫綱要)。

所謂的載入 schema,跟執行全部 migrations 是不一樣的,請參照 schema.rb.

4.2 Migration 可以很精確

如果,要單單執行某個特定的 migration,那可以用 db:migrate:updb:migrate:down 這兩項任務。只要寫上版本編號,就可以引發它的 updown 方法:

rake db:migrate:up VERSION=20080906120000

這會執行 20080906120000 這一版的 migration 的 up 方法。db:migrate:updb:migrate:down 會去確認這個 migration 目前有沒有跑過,所以,如果 Active Record 認為 20080906120000 已經跑過了,那麼執行 db:migrate:up VERSION=20080906120000 就不會做任何動作。

4.3 Migration 也可以很健談

依照預設值,migrations 會告訴你它們在做什麼、花多久時間。比方說,建立資料表並且加入索引(index)的話,會產生這樣的輸出結果(output):

20080906170109 CreateProducts: migrating -- create_table(:products) -> 0.0021s -- add_index(:products, :name) -> 0.0026s 20080906170109 CreateProducts: migrated (0.0059s)

想要控制這些 output 的話,可以用這幾個方法:

  • suppress_messages 抑制這個程式碼區塊(block)的任何輸出結果
  • say 輸出一段文字(第二個參數可以指定是否要縮排)
  • say_with_time 輸出一段文字,以及這段程式碼區塊要花多少時間跑完。如果程式區塊中回傳了一個整數,這個數字是說受影響的資料(rows)有多少。

例如:

class CreateProducts < ActiveRecord::Migration def self.up 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 def self.down drop_table :products end end

會產生這樣的輸出結果:

20080906170109 CreateProducts: migrating Created a table -> and an index! Waiting for a while -> 10.0001s -> 250 rows 20080906170109 CreateProducts: migrated (10.0097s)

如果您希望 Active Record 閉嘴,那麼執行 rake db:migrate VERBOSE=false 就可以抑制任何輸出結果。

5 在 Migrations 中使用 Models

在 migration 中,不管是建立新的資料,還是更新現有的資料,多少都會用到一個 Model,畢竟 Model 存在的目的,就是為了方便我們處理底層的資料。這當然是沒問題的,但有些地方需要留意一下。

假設有這樣的一個狀況:有個 migration 要用 Product Model,去更新資料表中的某一列。然後,艾莉絲更新了 Product Model,新加入一欄,還在上面設定了驗證機制(validation)。接著,鮑伯收假回來上班,更新了原始碼並且用 rake db:migrate 執行所有還沒跑過的 migrations,其中,包括了要用到 Product Model 的那一個 migration。此時,由於原始碼已經更新了,所以 Product Model 裡已經加入了艾莉絲所設定的驗證機制,但是,資料庫卻還是舊的,裡面沒有所謂的驗證欄,結果,因為驗證設定在一個不存在的欄上面,就出現了錯誤。

很多時候只是想要更新資料庫中的某幾列,既不想動手寫 SQL,又沒有要用到 Model 裡的其餘功能。為了應付這種狀況,一個解法是在 migration 內部定義一個 Model 副本:

class AddPartNumberToProducts < ActiveRecord::Migration class Product < ActiveRecord::Base end def self.up ... end def self.down ... end end

這個 migration 裡面,有份 Product Model 的迷你副本,因此,就不用理會應用程式裡面的 Product Model 了。

5.1 如何處理修改 Models 欄位

考量到效能議題,Model 裡面有什麼欄位的資訊,是會被快取起來的,如果在資料表中新增一欄,然後又在 migration 中立即再使用這個 Model 來新增資料,此時可能會用到舊的欄位資訊來新增這一列。如果要強制 Active Record 重讀欄位資訊,可以用 reset_column_information 方法:

class AddPartNumberToProducts < ActiveRecord::Migration class Product < ActiveRecord::Base end def self.up add_column :product, :part_number, :string Product.reset_column_information ... end def self.down ... end end

6 Schema Dumping(倒出資料庫綱要)

6.1 Schema 檔案是幹嘛用的?

雖然我們用 Migrations 來定義 database schema(資料庫綱要),但是當我們需要一次看到完整精確的 schema 時,Migrations 卻無法勝任 (因為 Migrations 是分開來執行的步驟)。這個機制是由 db/schema.rb 或是 Active Record 檢驗資料庫後所產生的 SQL 檔來擔任。它們不是拿來編輯的,只是純粹代表著資料庫的 schema 現況。

當要佈署新的應用程式時,並不需要把整個 Migrations 歷程全部重跑一遍(這樣搞也麻煩)。取而代之的是,只要把目前 schema 綱要,載入新的資料庫就可以了。又快,又簡單。

例如,這就是建立一個測試用資料庫所做的行為。把目前開發階段的資料庫倒出(看是 db/schema.rbdb/development.sql 皆可),然後再載入到測試資料庫。

另外,如果要快速瀏覽 Active Record 物件裡面有哪些屬性(attributes),schema 檔也很有用。關於 Active Record 物件屬性的資訊,並不在 Model 的程式碼裡。而且可能散佈在好幾個 migrations 之間,但是最終呢,都會整理在 schema 檔之中。其次,有個外掛叫做 annotate_models ,可以把 schema 的結果自動用註解的方式,放在每個 Model 程式上方,這個也可以用用看。

6.2 倒出資料庫綱要(Schema)的方式

要倒出 schema 有 :sql:ruby 兩種方式。至於是用哪種方式,可以用 config/environment.rb 裡面的 config.active_record.schema_format 來設定。

如果設定成用 :ruby 的話,schema 就會存在 db/schema.rb 裡面。這個檔案,看起來就像是一個超大的 migration:

ActiveRecord::Schema.define(:version => 20080906171750) 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 等方法,來表達資料庫的結構。由於 schema 是獨立於資料庫系統的(database independent),只要是 Active Record 有支援的資料庫系統,它都可以載入。如果,您的應用程式要散佈到多種資料庫系統,這點會非常有用。

不過,這也是有考量的:db/schema.rb 沒辦法表達出特定資料庫所專屬的功能,像是外部鍵約束(foreign key constraints)、觸發(triggers)或是預存程序(stored procedures)。雖然,migration 裡面可以執行自訂的 SQL 陳述句,但是 schema dumper 卻無法從資料庫中將它們重組回來。如果,您要用到這類自訂 SQL 的功能,那就必須把 schema 的格式設定成 :sql

設定成 :sql 的話,就不是用 Active Record 來倒出 Schema 了,而是用該資料庫系統的專屬工具,從 db:structure:dump 這個 Rake 任務,倒進 db/#{Rails.env}_structure.sql 裡面。比方說,用 PostgreSQL 的話,就是用 pg_dump 這個工具。用 MySQL 的話,則是各個資料表的 SHOW CREATE TABLE 輸出結果。載入這些 schema 只不過是執行檔案裡的 SQL 陳述句如此而已。

:sql 方式是可以完美的複製資料庫結構沒錯。不過,如果換一個不同的資料庫系統,那就往往沒辦法將 schema 寫入新的資料庫了。

6.3 倒出資料庫綱要(Schema)以及版本控制

既然 schema dumps 是資料庫結構的精確來源,這麼重要的東西,強烈建議您把它登錄到版本控制系統內。

7 Active Record 與 Referential Integrity(參照完整性)

所謂的 Active Record 之道,認為有頭腦的應該是 Models,而不是資料庫。因此,像是觸發(triggers)或外部鍵約束(foreign key constraints)這類偏向資料庫的功能,在 Rails 中就比較不常使用。

要確保資料完整的話,在 Model 裡面,可以用 validates_uniqueness_of 來做驗證。Model 裡面,資料庫關連(associations)上有個 :dependent 選項,可以設定成,在父物件消滅(destroy)的時候,自動連子物件一起消滅。不過,就像其他在應用程式層級操作的東西一樣,這並不能保證資料表之間的參照完整性,所以,有些人用外部鍵約束(foreign key constraints)來增進這個功能。

關於外部鍵約束的功能,Active Record 並沒有提供可以直接編輯它的工具,不過,還是可以用 execute 方法來執行任意的 SQL。也有不少的外掛,可以幫 Active Record 加上外部鍵的支援(還可以把外部鍵的設定倒給 db/schema.rb),像是這個 foreign_key_migrations

8 文件修改記錄

Lighthouse ticket

9 關於譯者

10 翻譯詞彙

本文翻譯自 http://guides.rails.info/migrations.html 。英文與繁體中文的對照詞彙,請見 rails-guide-glossary-list-zh_tw