# Active Record 數據庫遷移
遷移是 Active Record 提供的一個功能,按照時間順序管理數據庫模式。使用遷移,無需編寫 SQL,使用簡單的 Ruby DSL 就能修改數據表。
讀完本文,你將學到:
* 生成遷移文件的生成器;
* Active Record 提供用來修改數據庫的方法;
* 管理遷移和數據庫模式的 Rake 任務;
* 遷移和 `schema.rb` 文件的關系;
### Chapters
1. [遷移簡介](#%E8%BF%81%E7%A7%BB%E7%AE%80%E4%BB%8B)
2. [創建遷移](#%E5%88%9B%E5%BB%BA%E8%BF%81%E7%A7%BB)
* [單獨創建遷移](#%E5%8D%95%E7%8B%AC%E5%88%9B%E5%BB%BA%E8%BF%81%E7%A7%BB)
* [模型生成器](#%E6%A8%A1%E5%9E%8B%E7%94%9F%E6%88%90%E5%99%A8)
* [支持的類型修飾符](#%E6%94%AF%E6%8C%81%E7%9A%84%E7%B1%BB%E5%9E%8B%E4%BF%AE%E9%A5%B0%E7%AC%A6)
3. [編寫遷移](#%E7%BC%96%E5%86%99%E8%BF%81%E7%A7%BB)
* [創建數據表](#%E5%88%9B%E5%BB%BA%E6%95%B0%E6%8D%AE%E8%A1%A8)
* [創建聯合數據表](#%E5%88%9B%E5%BB%BA%E8%81%94%E5%90%88%E6%95%B0%E6%8D%AE%E8%A1%A8)
* [修改數據表](#%E4%BF%AE%E6%94%B9%E6%95%B0%E6%8D%AE%E8%A1%A8)
* [如果幫助方法不夠用](#%E5%A6%82%E6%9E%9C%E5%B8%AE%E5%8A%A9%E6%96%B9%E6%B3%95%E4%B8%8D%E5%A4%9F%E7%94%A8)
* [使用 `change` 方法](#%E4%BD%BF%E7%94%A8-change-%E6%96%B9%E6%B3%95)
* [使用 `reversible` 方法](#%E4%BD%BF%E7%94%A8-reversible-%E6%96%B9%E6%B3%95)
* [使用 `up` 和 `down` 方法](#%E4%BD%BF%E7%94%A8-up-%E5%92%8C-down-%E6%96%B9%E6%B3%95)
* [撤銷之前的遷移](#%E6%92%A4%E9%94%80%E4%B9%8B%E5%89%8D%E7%9A%84%E8%BF%81%E7%A7%BB)
4. [運行遷移](#%E8%BF%90%E8%A1%8C%E8%BF%81%E7%A7%BB)
* [回滾](#%E5%9B%9E%E6%BB%9A)
* [搭建數據庫](#%E6%90%AD%E5%BB%BA%E6%95%B0%E6%8D%AE%E5%BA%93)
* [重建數據庫](#%E9%87%8D%E5%BB%BA%E6%95%B0%E6%8D%AE%E5%BA%93)
* [運行指定的遷移](#%E8%BF%90%E8%A1%8C%E6%8C%87%E5%AE%9A%E7%9A%84%E8%BF%81%E7%A7%BB)
* [在不同的環境中運行遷移](#%E5%9C%A8%E4%B8%8D%E5%90%8C%E7%9A%84%E7%8E%AF%E5%A2%83%E4%B8%AD%E8%BF%90%E8%A1%8C%E8%BF%81%E7%A7%BB)
* [修改運行遷移時的輸出](#%E4%BF%AE%E6%94%B9%E8%BF%90%E8%A1%8C%E8%BF%81%E7%A7%BB%E6%97%B6%E7%9A%84%E8%BE%93%E5%87%BA)
5. [修改現有的遷移](#%E4%BF%AE%E6%94%B9%E7%8E%B0%E6%9C%89%E7%9A%84%E8%BF%81%E7%A7%BB)
6. [導出模式](#%E5%AF%BC%E5%87%BA%E6%A8%A1%E5%BC%8F)
* [模式文件的作用](#%E6%A8%A1%E5%BC%8F%E6%96%87%E4%BB%B6%E7%9A%84%E4%BD%9C%E7%94%A8)
* [導出的模式文件類型](#%E5%AF%BC%E5%87%BA%E7%9A%84%E6%A8%A1%E5%BC%8F%E6%96%87%E4%BB%B6%E7%B1%BB%E5%9E%8B)
* [模式導出和版本控制](#%E6%A8%A1%E5%BC%8F%E5%AF%BC%E5%87%BA%E5%92%8C%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6)
7. [Active Record 和引用完整性](#active-record-%E5%92%8C%E5%BC%95%E7%94%A8%E5%AE%8C%E6%95%B4%E6%80%A7)
8. [遷移和種子數據](#%E8%BF%81%E7%A7%BB%E5%92%8C%E7%A7%8D%E5%AD%90%E6%95%B0%E6%8D%AE)
### 1 遷移簡介
遷移使用一種統一、簡單的方式,按照時間順序修改數據庫的模式。遷移使用 Ruby DSL 編寫,因此不用手動編寫 SQL 語句,對數據庫的操作和所用的數據庫種類無關。
你可以把每個遷移看做數據庫的一個修訂版本。數據庫中一開始什么也沒有,各個遷移會添加或刪除數據表、字段或記錄。Active Record 知道如何按照時間線更新數據庫,不管數據庫現在的模式如何,都能更新到最新結構。同時,Active Record 還會更新 `db/schema.rb` 文件,匹配最新的數據庫結構。
下面是一個遷移示例:
```
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
```
這個遷移創建了一個名為 `products` 的表,然后在表中創建字符串字段 `name` 和文本字段 `description`。名為 `id` 的主鍵字段會被自動創建。`id` 字段是所有 Active Record 模型的默認主鍵。`timestamps` 方法創建兩個字段:`created_at` 和 `updated_at`。如果數據表中有這兩個字段,Active Record 會負責操作。
注意,對數據庫的改動按照時間向前 推移。運行遷移之前,數據表還不存在。運行遷移后,才會創建數據表。Active Record 知道如何撤銷遷移,如果回滾這次遷移,數據表會被刪除。
在支持事務的數據庫中,對模式的改動會在一個事務中執行。如果數據庫不支持事務,遷移失敗時,成功執行的操作將無法回滾。如要回滾,必須手動改回來。
某些查詢無法在事務中運行。如果適配器支持 DDL 事務,可以在某個遷移中調用 `disable_ddl_transaction!` 方法禁用。
如果想在遷移中執行 Active Record 不知如何撤銷的操作,可以使用 `reversible` 方法:
```
class ChangeProductsPrice < ActiveRecord::Migration
def change
reversible do |dir|
change_table :products do |t|
dir.up { t.change :price, :string }
dir.down { t.change :price, :integer }
end
end
end
end
```
或者不用 `change` 方法,分別使用 `up` 和 `down` 方法:
```
class ChangeProductsPrice < ActiveRecord::Migration
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
```
### 2 創建遷移
#### 2.1 單獨創建遷移
遷移文件存儲在 `db/migrate` 文件夾中,每個遷移保存在一個文件中。文件名采用 `YYYYMMDDHHMMSS_create_products.rb` 形式,即一個 UTC 時間戳后加以下劃線分隔的遷移名。遷移的類名(駝峰式)要和文件名時間戳后面的部分匹配。例如,在 `20080906120000_create_products.rb` 文件中要定義 `CreateProducts` 類;在 `20080906120001_add_details_to_products.rb` 文件中要定義 `AddDetailsToProducts` 類。文件名中的時間戳決定要運行哪個遷移,以及按照什么順序運行。從其他程序中復制遷移,或者自己生成遷移時,要注意運行的順序。
自己計算時間戳不是件簡單的事,所以 Active Record 提供了一個生成器:
```
$ rails generate migration AddPartNumberToProducts
```
這個命令生成一個空的遷移,但名字已經起好了:
```
class AddPartNumberToProducts < ActiveRecord::Migration
def change
end
end
```
如果遷移的名字是“AddXXXToYYY”或者“RemoveXXXFromYYY”這種格式,而且后面跟著一個字段名和類型列表,那么遷移中會生成合適的 `add_column` 或 `remove_column` 語句。
```
$ rails generate migration AddPartNumberToProducts part_number:string
```
這個命令生成的遷移如下:
```
class AddPartNumberToProducts < ActiveRecord::Migration
def change
add_column :products, :part_number, :string
end
end
```
如果想為新建的字段創建添加索引,可以這么做:
```
$ rails generate migration AddPartNumberToProducts part_number:string:index
```
這個命令生成的遷移如下:
```
class AddPartNumberToProducts < ActiveRecord::Migration
def change
add_column :products, :part_number, :string
add_index :products, :part_number
end
end
```
類似地,還可以生成刪除字段的遷移:
```
$ rails generate migration RemovePartNumberFromProducts part_number:string
```
這個命令生成的遷移如下:
```
class RemovePartNumberFromProducts < ActiveRecord::Migration
def change
remove_column :products, :part_number, :string
end
end
```
遷移生成器不單只能創建一個字段,例如:
```
$ rails generate migration AddDetailsToProducts part_number:string price:decimal
```
生成的遷移如下:
```
class AddDetailsToProducts < ActiveRecord::Migration
def change
add_column :products, :part_number, :string
add_column :products, :price, :decimal
end
end
```
如果遷移名是“CreateXXX”形式,后面跟著一串字段名和類型聲明,遷移就會創建名為“XXX”的表,以及相應的字段。例如:
```
$ rails generate migration CreateProducts name:string part_number:string
```
生成的遷移如下:
```
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.string :name
t.string :part_number
end
end
end
```
生成器生成的只是一些基礎代碼,你可以根據需要修改 `db/migrate/YYYYMMDDHHMMSS_add_details_to_products.rb` 文件,增刪代碼。
在生成器中還可把字段類型設為 `references`(還可使用 `belongs_to`)。例如:
```
$ rails generate migration AddUserRefToProducts user:references
```
生成的遷移如下:
```
class AddUserRefToProducts < ActiveRecord::Migration
def change
add_reference :products, :user, index: true
end
end
```
這個遷移會創建 `user_id` 字段,并建立索引。
如果遷移名中包含 `JoinTable`,生成器還會創建聯合數據表:
```
rails g migration CreateJoinTableCustomerProduct customer product
```
生成的遷移如下:
```
class CreateJoinTableCustomerProduct < ActiveRecord::Migration
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.2 模型生成器
模型生成器和腳手架生成器會生成合適的遷移,創建模型。遷移中會包含創建所需數據表的代碼。如果在生成器中指定了字段,還會生成創建字段的代碼。例如,運行下面的命令:
```
$ rails generate model Product name:string description:text
```
會生成如下的遷移:
```
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
```
字段的名字和類型數量不限。
#### 2.3 支持的類型修飾符
在字段類型后面,可以在花括號中添加選項。可用的修飾符如下:
* `limit`:設置 `string/text/binary/integer` 類型字段的最大值;
* `precision`:設置 `decimal` 類型字段的精度,即數字的位數;
* `scale`:設置 `decimal` 類型字段小數點后的數字位數;
* `polymorphic`:為 `belongs_to` 關聯添加 `type` 字段;
* `null`:是否允許該字段的值為 `NULL`;
例如,執行下面的命令:
```
$ rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic}
```
生成的遷移如下:
```
class AddDetailsToProducts < ActiveRecord::Migration
def change
add_column :products, :price, :decimal, precision: 5, scale: 2
add_reference :products, :supplier, polymorphic: true, index: true
end
end
```
### 3 編寫遷移
使用前面介紹的生成器生成遷移后,就可以開始寫代碼了。
#### 3.1 創建數據表
`create_table` 方法最常用,大多數時候都會由模型或腳手架生成器生成。典型的用例如下:
```
create_table :products do |t|
t.string :name
end
```
這個遷移會創建 `products` 數據表,在數據表中創建 `name` 字段(后面會介紹,還會自動創建 `id` 字段)。
默認情況下,`create_table` 方法會創建名為 `id` 的主鍵。通過 `:primary_key` 選項可以修改主鍵名(修改后別忘了修改相應的模型)。如果不想生成主鍵,可以傳入 `id: false` 選項。如果設置數據庫的選項,可以在 `:options` 選擇中使用 SQL。例如:
```
create_table :products, options: "ENGINE=BLACKHOLE" do |t|
t.string :name, null: false
end
```
這樣設置之后,會在創建數據表的 SQL 語句后面加上 `ENGINE=BLACKHOLE`。(MySQL 默認的選項是 `ENGINE=InnoDB`)
#### 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}
```
這段代碼會把 `product_id` 和 `category_id` 字段的 `:null` 選項設為 `true`。
如果想修改數據表的名字,可以傳入 `: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 修改數據表
有一個和 `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
```
這段代碼刪除了 `description` 和 `name` 字段,創建 `part_number` 字符串字段,并建立索引,最后重命名 `upccode` 字段。
#### 3.4 如果幫助方法不夠用
如果 Active Record 提供的幫助方法不夠用,可以使用 `execute` 方法,執行任意的 SQL 語句:
```
Product.connection.execute('UPDATE `products` SET `price`=`free` WHERE 1')
```
各方法的詳細用法請查閱 API 文檔:
* [`ActiveRecord::ConnectionAdapters::SchemaStatements`](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html):包含可在 `change`,`up` 和 `down` 中使用的方法;
* [`ActiveRecord::ConnectionAdapters::TableDefinition`](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html):包含可在 `create_table` 方法的塊參數上調用的方法;
* [`ActiveRecord::ConnectionAdapters::Table`](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html):包含可在 `change_table` 方法的塊參數上調用的方法;
#### 3.5 使用 `change` 方法
`change` 是遷移中最常用的方法,大多數情況下都能完成指定的操作,而且 Active Record 知道如何撤這些操作。目前,在 `change` 方法中只能使用下面的方法:
* `add_column`
* `add_index`
* `add_reference`
* `add_timestamps`
* `create_table`
* `create_join_table`
* `drop_table`(必須提供代碼塊)
* `drop_join_table`(必須提供代碼塊)
* `remove_timestamps`
* `rename_column`
* `rename_index`
* `remove_reference`
* `rename_table`
只要在塊中不使用 `change`、`change_default` 或 `remove` 方法,`change_table` 中的操作也是可逆的。
如果要使用任何其他方法,可以使用 `reversible` 方法,或者不定義 `change` 方法,而分別定義 `up` 和 `down` 方法。
#### 3.6 使用 `reversible` 方法
Active Record 可能不知如何撤銷復雜的遷移操作,這時可以使用 `reversible` 方法指定運行遷移和撤銷遷移時怎么操作。例如:
```
class ExampleMigration < ActiveRecord::Migration
def change
create_table :products do |t|
t.references :category
end
reversible do |dir|
dir.up do
#add a foreign key
execute <<-SQL
ALTER TABLE products
ADD CONSTRAINT fk_products_categories
FOREIGN KEY (category_id)
REFERENCES categories(id)
SQL
end
dir.down do
execute <<-SQL
ALTER TABLE products
DROP FOREIGN KEY fk_products_categories
SQL
end
end
add_column :users, :home_page_url, :string
rename_column :users, :email, :email_address
end
```
使用 `reversible` 方法還能確保操作按順序執行。在上面的例子中,如果撤銷遷移,`down` 代碼塊會在 `home_page_url` 字段刪除后、`products` 數據表刪除前運行。
有時,遷移的操作根本無法撤銷,例如刪除數據。這是,可以在 `down` 代碼塊中拋出 `ActiveRecord::IrreversibleMigration` 異常。如果有人嘗試撤銷遷移,會看到一個錯誤消息,告訴他無法撤銷。
#### 3.7 使用 `up` 和 `down` 方法
在遷移中可以不用 `change` 方法,而用 `up` 和 `down` 方法。`up` 方法定義要對數據庫模式做哪些操作,`down` 方法用來撤銷這些操作。也就是說,如果執行 `up` 后立即執行 `down`,數據庫的模式應該沒有任何變化。例如,在 `up` 中創建了數據表,在 `down` 方法中就要將其刪除。撤銷時最好按照添加的相反順序進行。前一節中的 `reversible` 用法示例代碼可以改成:
```
class ExampleMigration < ActiveRecord::Migration
def 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 down
rename_column :users, :email_address, :email
remove_column :users, :home_page_url
execute <<-SQL
ALTER TABLE products
DROP FOREIGN KEY fk_products_categories
SQL
drop_table :products
end
end
```
如果遷移不可撤銷,應該在 `down` 方法中拋出 `ActiveRecord::IrreversibleMigration` 異常。如果有人嘗試撤銷遷移,會看到一個錯誤消息,告訴他無法撤銷。
#### 3.8 撤銷之前的遷移
Active Record 提供了撤銷遷移的功能,通過 `revert` 方法實現:
```
require_relative '2012121212_example_migration'
class FixupExampleMigration < ActiveRecord::Migration
def change
revert ExampleMigration
create_table(:apples) do |t|
t.string :variety
end
end
end
```
`revert` 方法還可接受一個塊,定義撤銷操作。`revert` 方法可用來撤銷以前遷移的部分操作。例如,`ExampleMigration` 已經執行,但后來覺得最好還是序列化產品列表。那么,可以編寫下面的代碼:
```
class SerializeProductListMigration < ActiveRecord::Migration
def change
add_column :categories, :product_list
reversible do |dir|
dir.up do
# transfer data from Products to Category#product_list
end
dir.down do
# create Products from Category#product_list
end
end
revert do
# copy-pasted code from ExampleMigration
create_table :products do |t|
t.references :category
end
reversible do |dir|
dir.up do
#add a foreign key
execute <<-SQL
ALTER TABLE products
ADD CONSTRAINT fk_products_categories
FOREIGN KEY (category_id)
REFERENCES categories(id)
SQL
end
dir.down do
execute <<-SQL
ALTER TABLE products
DROP FOREIGN KEY fk_products_categories
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 提供了很多 Rake 任務,用來執行指定的遷移。
其中最常使用的是 `rake db:migrate`,執行還沒執行的遷移中的 `change` 或 `up` 方法。如果沒有未運行的遷移,直接退出。`rake db:migrate` 按照遷移文件名中時間戳順序執行遷移。
注意,執行 `db:migrate` 時還會執行 `db:schema:dump`,更新 `db/schema.rb` 文件,匹配數據庫的結構。
如果指定了版本,Active Record 會運行該版本之前的所有遷移。版本就是遷移文件名前的數字部分。例如,要運行 20080906120000 這個遷移,可以執行下面的命令:
```
$ rake db:migrate VERSION=20080906120000
```
如果 20080906120000 比當前的版本高,上面的命令就會執行所有 20080906120000 之前(包括 20080906120000)的遷移中的 `change` 或 `up` 方法,但不會運行 20080906120000 之后的遷移。如果回滾遷移,則會執行 20080906120000 之前(不包括 20080906120000)的遷移中的 `down` 方法。
#### 4.1 回滾
還有一個常用的操作時回滾到之前的遷移。例如,遷移代碼寫錯了,想糾正。我們無須查找遷移的版本號,直接執行下面的命令即可:
```
$ rake db:rollback
```
這個命令會回滾上一次遷移,撤銷 `change` 方法中的操作,或者執行 `down` 方法。如果想撤銷多個遷移,可以使用 `STEP` 參數:
```
$ rake db:rollback STEP=3
```
這個命令會撤銷前三次遷移。
`db:migrate:redo` 命令可以回滾上一次遷移,然后再次執行遷移。和 `db:rollback` 一樣,如果想重做多次遷移,可以使用 `STEP` 參數。例如:
```
$ rake db:migrate:redo STEP=3
```
這些 Rake 任務的作用和 `db:migrate` 一樣,只是用起來更方便,因為無需查找特定的遷移版本號。
#### 4.2 搭建數據庫
`rake db:setup` 任務會創建數據庫,加載模式,并填充種子數據。
#### 4.3 重建數據庫
`rake db:reset` 任務會刪除數據庫,然后重建,等價于 `rake db:drop db:setup`。
這個任務和執行所有遷移的作用不同。`rake db:reset` 使用的是 `schema.rb` 文件中的內容。如果遷移無法回滾,`rake db:reset` 起不了作用。詳細介紹參見“[導出模式](#schema-dumping-and-you)”一節。
#### 4.4 運行指定的遷移
如果想執行指定遷移,或者撤銷指定遷移,可以使用 `db:migrate:up` 和 `db:migrate:down` 任務,指定相應的版本號,就會根據需求調用 `change`、`up` 或 `down` 方法。例如:
```
$ rake db:migrate:up VERSION=20080906120000
```
這個命令會執行 20080906120000 遷移中的 `change` 方法或 `up` 方法。`db:migrate:up` 首先會檢測指定的遷移是否已經運行,如果 Active Record 任務已經執行,就不會做任何操作。
#### 4.5 在不同的環境中運行遷移
默認情況下,`rake db:migrate` 任務在 `development` 環境中執行。要在其他環境中運行遷移,執行命令時可以使用環境變量 `RAILS_ENV` 指定環境。例如,要在 `test` 環境中運行遷移,可以執行下面的命令:
```
$ rake db:migrate RAILS_ENV=test
```
#### 4.6 修改運行遷移時的輸出
默認情況下,運行遷移時,會輸出操作了哪些操作,以及花了多長時間。創建數據表并添加索引的遷移產生的輸出如下:
```
== CreateProducts: migrating =================================================
-- create_table(:products)
-> 0.0028s
== CreateProducts: migrated (0.0028s) ========================================
```
在遷移中可以使用很多方法,控制輸出:
| 方法 | 作用 |
| --- | --- |
| suppress_messages | 接受一個代碼塊,禁止代碼塊中所有操作的輸出 |
| say | 接受一個消息字符串作為參數,將其輸出。第二個參數是布爾值,指定輸出結果是否縮進 |
| say_with_time | 輸出文本,以及執行代碼塊中操作所用時間。如果代碼塊的返回結果是整數,會當做操作的記錄數量 |
例如,下面這個遷移:
```
class CreateProducts < ActiveRecord::Migration
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 輸出任何結果,可以使用 `rake db:migrate VERBOSE=false`。
### 5 修改現有的遷移
有時編寫的遷移中可能有錯誤,如果已經運行了遷移,不能直接編輯遷移文件再運行遷移。Rails 認為這個遷移已經運行,所以執行 `rake db:migrate` 任務時什么也不會做。這種情況必須先回滾遷移(例如,執行 `rake db:rollback` 任務),編輯遷移文件后再執行 `rake db:migrate` 任務執行改正后的版本。
一般來說,直接修改現有的遷移不是個好主意。這么做會為你以及你的同事帶來額外的工作量,如果這個遷移已經在生產服務器上運行過,還可能帶來不必要的麻煩。你應該編寫一個新的遷移,做所需的改動。編輯新生成還未納入版本控制的遷移(或者更寬泛地說,還沒有出現在開發設備之外),相對來說是安全的。
在新遷移中撤銷之前遷移中的全部操作或者部分操作可以使用 `revert` 方法。(參見前面的 [撤銷之前的遷移](#reverting-previous-migrations) 一節)
### 6 導出模式
#### 6.1 模式文件的作用
遷移的作用并不是為數據庫模式提供可信的參考源。`db/schema.rb` 或由 Active Record 生成的 SQL 文件才有這個作用。`db/schema.rb` 這些文件不可修改,其目的是表示數據庫的當前結構。
部署新程序時,無需運行全部的遷移。直接加載數據庫結構要簡單快速得多。
例如,測試數據庫是這樣創建的:導出開發數據庫的結構(存入文件 `db/schema.rb` 或 `db/structure.sql`),然后導入測試數據庫。
模式文件還可以用來快速查看 Active Record 中有哪些屬性。模型中沒有屬性信息,而且遷移會頻繁修改屬性,但是模式文件中有最終的結果。[annotate_models](https://github.com/ctran/annotate_models) gem 會在模型文件的頂部加入注釋,自動添加并更新模型的模式。
#### 6.2 導出的模式文件類型
導出模式有兩種方法,由 `config/application.rb` 文件中的 `config.active_record.schema_format` 選項設置,可以是 `:sql` 或 `:ruby`。
如果設為 `:ruby`,導出的模式保存在 `db/schema.rb` 文件中。打開這個文件,你會發現內容很多,就像一個很大的遷移:
```
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_table`、`add_index` 等方法審查數據庫的結構。這個文件盒使用的數據庫類型無關,可以導入任何一種 Active Record 支持的數據庫。如果開發的程序需要兼容多種數據庫,可以使用這個文件。
不過 `db/schema.rb` 也有缺點:無法執行數據庫的某些操作,例如外鍵約束,觸發器,存儲過程。在遷移文件中可以執行 SQL 語句,但導出模式的程序無法從數據庫中重建這些語句。如果你的程序用到了前面提到的數據庫操作,可以把模式文件的格式設為 `:sql`。
`:sql` 格式的文件不使用 Active Record 的模式導出程序,而使用數據庫自帶的導出工具(執行 `db:structure:dump` 任務),把數據庫模式導入 `db/structure.sql` 文件。例如,PostgreSQL 使用 `pg_dump` 導出模式。如果使用 MySQL,`db/structure.sql` 文件中會出現多個 `SHOW CREATE TABLE` 用來創建數據表的語句。
加載模式時,只要執行其中的 SQL 語句即可。按預期,導入后會創建一個完整的數據庫結構。使用 `:sql` 格式,就不能把模式導入其他類型的數據庫中了。
#### 6.3 模式導出和版本控制
因為導出的模式文件是數據庫模式的可信源,強烈推薦將其納入版本控制。
### 7 Active Record 和引用完整性
Active Record 在模型中,而不是數據庫中設置關聯。因此,需要在數據庫中實現的功能,例如觸發器、外鍵約束,不太常用。
`validates :foreign_key, uniqueness: true` 這個驗證是模型保證數據完整性的一種方法。在關聯中設置 `:dependent` 選項,可以保證父對象刪除后,子對象也會被刪除。和任何一種程序層的操作一樣,這些無法完全保證引用完整性,所以很多人還是會在數據庫中使用外鍵約束。
Active Record 并沒有為使用這些功能提供任何工具,不過 `execute` 方法可以執行任意的 SQL 語句。還可以使用 [foreigner](https://github.com/matthuhiggins/foreigner) 等 gem,為 Active Record 添加外鍵支持(還能把外鍵導出到 `db/schema.rb` 文件)。
### 8 遷移和種子數據
有些人使用遷移把數據存入數據庫:
```
class AddInitialProducts < ActiveRecord::Migration
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 代碼,然后執行 `rake db:seed` 命令即可:
```
5.times do |i|
Product.create(name: "Product ##{i}", description: "A product.")
end
```
填充新建程序的數據庫,使用這種方法操作起來簡潔得多。
### 反饋
歡迎幫忙改善指南質量。
如發現任何錯誤,歡迎修正。開始貢獻前,可先行閱讀[貢獻指南:文檔](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation)。
翻譯如有錯誤,深感抱歉,歡迎 [Fork](https://github.com/ruby-china/guides/fork) 修正,或至此處[回報](https://github.com/ruby-china/guides/issues/new)。
文章可能有未完成或過時的內容。請先檢查 [Edge Guides](http://edgeguides.rubyonrails.org) 來確定問題在 master 是否已經修掉了。再上 master 補上缺少的文件。內容參考 [Ruby on Rails 指南準則](ruby_on_rails_guides_guidelines.html)來了解行文風格。
最后,任何關于 Ruby on Rails 文檔的討論,歡迎到 [rubyonrails-docs 郵件群組](http://groups.google.com/group/rubyonrails-docs)。
- Ruby on Rails 指南 (651bba1)
- 入門
- Rails 入門
- 模型
- Active Record 基礎
- Active Record 數據庫遷移
- Active Record 數據驗證
- Active Record 回調
- Active Record 關聯
- Active Record 查詢
- 視圖
- Action View 基礎
- Rails 布局和視圖渲染
- 表單幫助方法
- 控制器
- Action Controller 簡介
- Rails 路由全解
- 深入
- Active Support 核心擴展
- Rails 國際化 API
- Action Mailer 基礎
- Active Job 基礎
- Rails 程序測試指南
- Rails 安全指南
- 調試 Rails 程序
- 設置 Rails 程序
- Rails 命令行
- Rails 緩存簡介
- Asset Pipeline
- 在 Rails 中使用 JavaScript
- 引擎入門
- Rails 應用的初始化過程
- Autoloading and Reloading Constants
- 擴展 Rails
- Rails 插件入門
- Rails on Rack
- 個性化Rails生成器與模板
- Rails應用模版
- 貢獻 Ruby on Rails
- Contributing to Ruby on Rails
- API Documentation Guidelines
- Ruby on Rails Guides Guidelines
- Ruby on Rails 維護方針
- 發布記
- A Guide for Upgrading Ruby on Rails
- Ruby on Rails 4.2 發布記
- Ruby on Rails 4.1 發布記
- Ruby on Rails 4.0 Release Notes
- Ruby on Rails 3.2 Release Notes
- Ruby on Rails 3.1 Release Notes
- Ruby on Rails 3.0 Release Notes
- Ruby on Rails 2.3 Release Notes
- Ruby on Rails 2.2 Release Notes