更多內容 rubyonrails.org: 更多內容

Active Record 遷移

Migration,遷移。Active Record 眾多功能之一,可與時俱進的管理資料庫綱要。最棒的是遷移提供了簡潔的 Ruby DSL,無需寫純 SQL,便能變更資料表。

讀完本篇,您將了解:

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 null: false
    end
  end
end

create_table :products do |t| 新增了一張 products 的資料表,有 name (類型是字串)、description(類型是 text)的欄位。也會自動新增主鍵(id)(遷移裡看不到),所有 Active Record Model 的預設主鍵名稱都叫做 idtimestamps 宏新稱了兩個欄位, created_atupdated_at,Active Record 會負責處理特殊欄位(主鍵、時間戳章),無需自己處理。

注意到我們定義了一個 change 方法,內容填入時間往前時期望的變動。在這筆遷移執行之前,資料庫裡還沒有資料表。遷移之後資料表便建出來了。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

也可以用 updown 來取代 change

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 形式命名:

YYYYMMDDHHMMSS_migration_name.rb,前面的 YYYYMMDDHHMMSS 是 UTC 格式的時間戳章,接著是底線,底線後面是該筆遷移的名稱。遷移類別以駝峰形式命名,會對應到 _migration_name。舉例來說 20140916204300_create_products.rb 會定義出 CreateProducts 這樣的類別名稱。而 20121027111111_add_details_to_products.rb 則會定義出 AddDetailsToProducts 這樣的類別名稱。Rails 根據時間戳章決定執行的先後順序。若是從別的應用程式複製過來的遷移檔案,或是自己產生的遷移,要注意執行的順序。

當然了,計算時間戳章很難,所以 Active Record 提供了產生器,幫您處理好時間戳的問題:

$ rails generate migration AddPartNumberToProducts

會產生出空的遷移檔案,遷移的類別名稱已經取好了:

class AddPartNumberToProducts < ActiveRecord::Migration
  def change
  end
end

在命令列輸入的遷移名稱若是 AddXXXToYYYRemoveXXXFromYYY,之後接一系列的欄位名稱與類型。則會自動產生 add_columnremove_column

$ rails generate migration AddPartNumberToProducts part_number:string

會產生

class AddPartNumberToProducts < ActiveRecord::Migration
  def change
    add_column :products, :part_number, :string
  end
end

給欄位加上索引(index)也是很簡單的:

$ 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

剛剛已經看過兩種常見的遷移命名形式:AddXXXToYYYRemoveXXXFromYYY,還有 CreateXXX 這種,後面接欄位名與類型:

$ rails generate migration CreateProducts name:string part_number:string

則會新建 table 及欄位:

class CreateProducts < ActiveRecord::Migration
  def change
    create_table :products do |t|
      t.string :name
      t.string :part_number
    end
  end
end

Rails 產生的遷移檔案不過是個開始,可以透過編輯 db/migrate/YYYYMMDDHHMMSS_add_details_to_products.rb 檔案,根據需求修改。

還有一種欄位類型叫做 references(= belongs_to):

$ rails generate migration AddUserRefToProducts user:references
# 等同於
$ rails generate migration AddUserRefToProducts user:belongs_to

會產生

class AddUserRefToProducts < ActiveRecord::Migration
  def change
    add_reference :products, :user, index: true
  end
end

會給 Product 資料表,產生一個 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 Model 產生器

Model 與鷹架產生器新建 Model 時,也會建立遷移。這個遷移檔案會包含建立相關資料表的步驟。若進一步告訴 Rails 所需的欄位,欄位也會加入至遷移檔案裡。舉例來說,執行:

$ 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 null: false
    end
  end
end

可以接著給產生出來的遷移檔案新增欄位。

2.3 傳入類型修飾符

某些常用的類型修飾符可以直接在命令列指定。這些修飾符在欄位類型之後指定,用大括號包起來:

舉例來說,執行:

$ 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 是最基礎的方法之一,通常 rails generate modelrails generate scaffold 便會自動產生出來。常見用途:

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

會建立一張 products 資料表,有著 name 欄位(以及看不見的主鍵 id)。

create_table 預設會產生主鍵(id),可以用 :primary_key 選項來修改主鍵的名字(記得更新對應的 Model)。或者是完全不要主鍵,可以傳入 id: false 選項。資料庫特定的選項,可以傳給 :options

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

會在用來建立資料表的 SQL 語句,附上 ENGINE=BLACKHOLE(使用 MySQL 預設是 ENGINE=InnoDB)。

更多細節可查閱 create_table API。

3.2 建立連接資料表

create_join_table 會建立一張 HABTM (has_and_belongs_to_many)連接表。常見的應用場景:

create_join_table :products, :categories

會建立一張 categories_products 資料表,有著 category_idproduct_id 欄位。這些欄位的預設選項是 null: false,可以在 :column_options 修改預設值:

create_join_table :products, :categories, column_options: {null: true}

會建立 product_idcategory_id 欄位,配上 null: true 選項。

若要修改連接資料表的名字,使用 table_name: 選項:

create_join_table :products, :categories, table_name: :categorization

便會產生出 categorization 資料表,一樣有 category_idproduct_id

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 修改欄位

就像 Rails 有 remove_columnadd_column,Rails 也提供了 change_column 方法。

change_column :products, :part_number, :text

會更改 products 資料表裡的 part_number 欄位,類型改為 :text

除了 change_column 之外,還有 change_column_nullchange_column_default 方法,專門用來修改欄位的預設值。

change_column_null :products, :name, false
change_column_default :products, :approved, false

上例程式會把 products 資料表的 :name 欄位設為 NOT NULL;而 :approved 欄位預設設為 false

change_column(以及 change_column_default)不同,change_column_null 是可逆的。

3.5 欄位修飾符

欄位修飾符可在新建或修改欄位時使用:

修飾符 說明
:limit 設定 string/text/binary/integer 欄位的最大值。
:precision 定義 decimal 欄位的精度,含小數點可以有幾個數字。
:scale 定義 decimal 欄位的位數,小數點可以有幾位。
:polymorphic belongs_to association 加上 type 欄位。
:null 欄位允不允許 NULL 值。
:default 允許設定欄位的預設值。注意,若使用了動態數值(譬如日期),預設值只會在第一次跑遷移時做計算(也就是跑遷移當下的日期)。
:index 給欄位加入索引。

某些連接器可能支援其他選項,請參考與連接器相關的 API 文件來了解更多資訊。

3.6 外鍵

雖然不是必須的,但可能會想加入外鍵約束來保證參照的完整性

add_foreign_key :articles, :authors

上例會給 articles 資料表新增外鍵欄位:author_id。外鍵會使用 articles 資料表的主鍵 id 作為參照。若參照的欄位名稱不能從資料表名稱推測出來,可以使用 :columnprimary_key 選項。

Rails 會給每個外鍵產生一個名字,由 fk_rails_ 前綴加上 10 個隨機字元。可以用 :name 選項來修改。

Active Record 只支援單欄外鍵。需要用組合外鍵請使用 executestructure.sql

移除外鍵也很簡單:

# 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

# remove foreign key by name
remove_foreign_key :accounts, name: :special_fk_name

3.7 輔助方法不夠用怎麼辦

Active Record 提供的輔助方法不夠用的時候,可以使用 execute 方法來執行任何 SQL 語句:

Product.connection.execute('UPDATE `products` SET `price`=`free` WHERE 1')

關於每個方法的更多細節與範例,請查閱 API 文件,特別是:

ActiveRecord::ConnectionAdapters::SchemaStatements (在 change, up and down 裡可用的方法有那些)

ActiveRecord::ConnectionAdapters::TableDefinition (傳入 create_table 區塊物件可用的方法有那些)

ActiveRecord::ConnectionAdapters::Table (傳入 change_table 區塊物件可用的方法有那些)

3.8 使用 change 方法

撰寫遷移檔案主要用 change 方法,適用於大多數情況,多數 Active Record 知道如何執行逆操作的情況。以下是目前 change 方法裡所支援的方法:

  • add_column
  • add_index
  • add_reference
  • add_timestamps
  • create_table
  • create_join_table
  • drop_table (must supply a block)
  • drop_join_table (must supply a block)
  • remove_timestamps
  • rename_column
  • rename_index
  • remove_reference
  • rename_table

change_table 也是可逆的,只要傳給 change_table 的區塊沒有呼叫 changechange_default 或是 remove 即可。

如果想使用其它的方法,可以使用 reversible 或是撰寫 updown 方法,而不是使用 change

3.9 使用 reversible

需要處理 Active Record 不知道怎麼變回來的複雜遷移時,可以使用 reversible 方法來指定遷移時要做什麼(up),回滾時要做什麼(down),比如:

class ExampleMigration < ActiveRecord::Migration
  def change
    create_table :distributors do |t|
      t.string :zipcode
    end

    reversible do |dir|
      dir.up do
        # add a CHECK constraint
        execute <<-SQL
          ALTER TABLE distributors
            ADD CONSTRAINT zipchk
              CHECK (char_length(zipcode) = 5) NO INHERIT;
        SQL
      end
      dir.down do
        execute <<-SQL
          ALTER TABLE distributors
            DROP CONSTRAINT zipchk
        SQL
      end
    end

    add_column :users, :home_page_url, :string
    rename_column :users, :email, :email_address
  end
end

使用 reversible 會確保執行順序的正確性。上例的遷移取消時(回滾),down 區塊會在 home_page_url 欄位移除前,以及 distributors 資料表刪除前,執行 down 區塊裡的內容。

有時候遷移做了怎麼樣都不可逆的操作,比如,可能是刪除資料。這種情況下,Active Record 會在試著取消遷移時,拋出一個 ActiveRecord::IrreversibleMigration,表示無法恢復先前的操作。

3.10 使用 updown 方法

可以不用 change 來撰寫遷移,而使用經典的 updown 寫法。

up 撰寫對資料庫綱要的變化(遷移)、down 撰寫取消 up 操作的操作(回滾)。兩個操作要可以互相抵消。舉例來說,up 建了一張資料表,則 down 便要 drop 該張資料表。取消遷移時最好依照遷移時的反序執行。上例使用 reversible 可以用 updown 改寫:

class ExampleMigration < ActiveRecord::Migration
  def up
    create_table :distributors do |t|
      t.string :zipcode
    end

    # add a CHECK constraint
    execute <<-SQL
      ALTER TABLE distributors
        ADD CONSTRAINT zipchk
        CHECK (char_length(zipcode) = 5);
    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 distributors
        DROP CONSTRAINT zipchk
    SQL

    drop_table :distributors
  end
end

如果遷移是不可逆的操作,要在 down 拋出一個 ActiveRecord::IrreversibleMigration。這樣子別的開發者試圖要取消遷移時,便會顯示這個遷移無法取消的錯誤訊息。

3.11 取消之前的遷移

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 方法也接受區塊,具體取消的操作寫在區塊裡,這在只取消部份的遷移的場景下很有用。舉個例子,假設 ExampleMigration 已經遷移了,之後覺得還是用 Active Record 的驗證,在 CHECK 約束條件的地方來驗證郵遞區號。

class DontUseConstraintForZipcodeValidationMigration < ActiveRecord::Migration
  def change
    revert do
      # copy-pasted code from ExampleMigration
      reversible do |dir|
        dir.up do
          # add a CHECK constraint
          execute <<-SQL
            ALTER TABLE distributors
              ADD CONSTRAINT zipchk
                CHECK (char_length(zipcode) = 5);
          SQL
        end
        dir.down do
          execute <<-SQL
            ALTER TABLE distributors
              DROP CONSTRAINT zipchk
          SQL
        end
      end

      # The rest of the migration was ok
    end
  end
end

同樣的遷移也可以不用 revert 處理,但會需要多做幾個步驟。把 create_tablereversible 順序對換,create_table 換成 drop_table,最後對換 up down 裡的程式碼。其實這就是 revert 做的事。

4 執行遷移

Rails 提供了一組 Rake 任務,用來執行特定的遷移。

第一個相關會用到的 Rake 任務是 rake db:migraterake db:migrate 任務最簡單的形式,不過是尚未執行的遷移裡面的 changeup 方法。若所有的遷移都執行完畢了便離開,否則按照時間戳的順序進行遷移。

有點要注意的是,執行 db:migrate 也會執行 db:schema:dump,會更新 db/schema.rb 來反映出當下的資料庫結構。

如果指定了目標版本,Active Record 會執行目標版本之前所有的遷移。目標版本的名稱是遷移名前綴的 UTC 時間戳章,比如 20080906120000

$ rake db:migrate VERSION=20080906120000

若版本 20080906120000 大於目前版本,則會執行 change(或 up)方法,遷移到 20080906120000(包含)。若版本 20080906120000 小於目前版本,則會對版本小於 20080906120000 (不包含)的遷移執行 down 方法。

4.1 回滾

最常見的任務便是回滾前次遷移。假設你犯了個錯誤,並想修正。與其找出前次的版本再執行,可以直接:

$ rake db:rollback

會取消上次的 change 操作,或是執行 down 方法,來回滾上一次遷移。可以指定要回滾幾步,使用 STEP 參數

$ rake db:rollback STEP=3

會回滾前 3 次遷移。

db:migrate:redo 用來回滾、接著再遷移一次。同樣接受 STEP 參數,比如往前回滾 3 次,再遷移:

$ rake db:migrate:redo STEP=3

這些操作用 db:migrate 都辦得到,只是方便你使用而已,因為不用特別指定要遷移或是回滾的版本號。

4.2 設定資料庫

The rake db:setup 會新建資料庫、載入資料庫綱要、並用種子資料來初始化資料庫。

4.3 重置資料庫

rake db:reset 會將資料庫移除,再重新建立。等同於 rake db:drop db:setup

這與執行所有的遷移不一樣。這只會用 schema.rb 裡的內容來操作。如果遷移不能回滾,rake db:reset 也是派不上用場的。了解更多請參考導出資料庫綱要

4.4 執行特定的遷移

如想執行特定的遷移,可以用 db:migrate:updb:migrate:down。只需要指定特定的版本,就會根據版本去呼叫 changeupdown 方法,比如:

$ rake db:migrate:up VERSION=20080906120000

會執行版本大於 20080906120000 的遷移裡面的 changeup 方法。若已經遷移過了,則 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 null: false
      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:rollback,編輯遷移檔案,接著再次執行 rake db:migrate

通常不太推薦修改現有的遷移,因為會增加同事更多工作量。尤其是遷移已經上線了(production),應該要寫個新的遷移,執行需要的修改,來達成想完成的事情。修改新建的遷移(尚未提交至版本管理)是相對無害的。

revert 方法用來寫新的遷移,取消先前的遷移的場景很有用。參考取消之前的遷移小節。

6 導出資料庫綱要

6.1 資料庫綱要檔案有什麼用

遷移是會變的,不會反映出當下的資料庫結構。要確定資料庫的結構,還是看資料庫綱要檔案: db/schema.rb 最可靠,或是由 Active Record 導生的 SQL 檔案。db/schema.rb 與 SQL 是用來表示資料庫目前的狀態,兩個檔案不可以修改。

依靠重新執行所有的遷移,來部署新的應用程式,不可靠又容易出錯。最簡單的辦法是把資料庫的結構檔案,加載到資料庫裡。

舉例來說,這便是測試資料庫如何產生的過程:導出目前的開發資料庫(導出成 db/schema.rbdb/structure.sql),接著載入至測試資料庫。

若想了解 Active Record object 有什麼屬性。Model 裡沒有寫,屬性散佈在多個遷移檔案裡,但所有的屬性都總結在資料庫綱要檔案了。如果想要在 Model 裡看到所有的屬性資訊,有一個 annotate_models RubyGem,自動在 Model 檔案最上方加註解,使用資料庫綱要檔案,來記錄每個 Model 有的屬性。

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_tableadd_index 這些輔助方法用來表示資料庫的結構。這與使用何種資料庫無關,可以加載到任何 Active Record 所支援的資料庫。如果應用程式要發佈到多種資料庫的時候,這個檔案非常有用。

但魚與熊掌不可兼得:db/schema.rb 不能表達資料庫特有的功能,像是觸發器(triggers)、或是儲存過程(stored procedure)。但在遷移裡可以執行任何自訂的 SQL 語句,但資料庫綱要的導出程式,無法從資料庫重建出這些 SQL 語句。若要執行自訂的 SQL,記得將資料庫綱要的導出格式設定為 :sql

與其使用 Active Record 提供的資料庫綱要導出程式,可以用特定資料庫的導出工具(透過 db:structure:dump 任務來導出 db/structure.sql)。舉例來說,PostgreSQL 使用 pg_dump 這個工具來導出 SQL。而 MySQL 呢,資料庫綱要只不過是多張資料表的 SHOW CREATE TABLE 的結果。

載入這些 :sql 格式的綱要檔案,不過是執行裡面的 SQL 語句而已。定義上來說,會建立一份資料庫結構的完美複本。但使用 :sql 綱要格式的話,便不能從一種 RDBMS 資料庫,切換到另一種 RDBMS 資料庫了。

6.3 導出資料庫綱要與版本管理

因為導出的資料庫綱要檔案,是資料庫結構最權威的來源,強烈建議將資料庫綱要檔案加到版本管理裡。

7 Active Record 與參照完整性

Active Record 認為事情要在 model 裡處理好,而不是在資料庫。也是因為這個原因,需要在資料庫實作的功能,不常使用像是觸發器或約束條件。

validates :foreign_key, uniqueness: true 這樣的驗證,是 Model 可以增強資料整合性的一種方法。:dependent 選項讓 Model 可以自動刪除關聯的資料。某些人認為像是這種操作,以及所有在應用程式層級執行的操作,無法保證參照的完整性,要跟外鍵約束一樣,放在資料庫解決才是。

雖然 Active Record 沒有直接提供任何工具來解決這件事,但可以用 execute 方法來執行任何的 SQL 語句。

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 有 “seeds” 這個功能,應該這麼用這個來給資料庫新增初始資料才對。用起來非常簡單,在 db/seeds.rb 寫些 Ruby,執行 rake db:seed 即可:

5.times do |i|
  Product.create(name: "Product ##{i}", description: "A product.")
end

這樣比用遷移來設定新應用程式的資料庫簡潔許多。

反饋

歡迎幫忙改善指南的品質。

如發現任何錯誤之處,歡迎修正。開始貢獻前,可以先閱讀貢獻指南:文件

翻譯如有錯誤,深感抱歉,歡迎 Fork 修正,或至此處回報

文章可能有未完成或過時的內容。請先檢查 Edge Guides 來確定問題在 master 是否已經修掉了。再上 master 補上缺少的文件。內容參考 Ruby on Rails 指南準則來了解行文風格。

最後,任何關於 Ruby on Rails 文件的討論,歡迎至 rubyonrails-docs 郵件論壇