[Rails] Active Record Migration (Database Migration)
參考資料:Active Record Migrations @ RailsGuides
# CLI
# 會去執行 ./lib/tasks/dev.rake,需先設定
$ bin/rails dev:build
# 把資料庫砍掉重建
$ bin/rails db:drop db:create db:migrate db:seed
# ./db/migrate/20170919064947_add_need_eticket_to_activities.rb
def change
# add_column :table_name, :column_name, :type, :options
add_column :activities, :need_eticket, :boolean, {comment: '是否使用電子票券', default: false}
end
使用 jsonb
$ rails g migration enable_hstore_extension
# migration 檔案中放入以下內容
enable_extension 'hstore'
使用 uuid
$ rails g migration enable_uuid_extension
# migration 檔案中放入以下內容
enable_extension 'uuid-ossp'
enable_extension 'pgcrypto'
觀念
Migration 是用來描述「資料庫的架構長什麼樣子」的檔案,它會隨著專案開發的過程中逐漸增加。有 了 Migration,記得要執行 rails db:migrate
指令,這樣就會把這些描述轉換成真實的資料表。
你可以把每一次的 migrate 都視為一次新「版本」的資料庫,因為 bin/rails db:migrate
這個指令只會針對還沒執行過的 Migration 檔案有效果,已經做過的 Migration ,再做一次是不會有反應的,所以即使修改同一個 Migration 檔再重新執行是沒用的。
透過 Generator 建立 migration 檔
我們可以利用 rails 中的 generator 來建立 migration 檔,migration 的檔案會存在 db/migrate/
中,其中 migration 檔案的檔名(以小寫底線或大寫駝峰命名)必須和 migration 檔中的 class (以大寫駝峰命名)相對應。
在尚未執行 db:migrate
前,利用 Generator 產生的 migration 檔案都可以在自己新增、移除或修改。
透過 model 和 scaffold Generator 也會自動產生 migration 檔。
Cheat Sheet
$ bin/rails g migration <migration_file_name>
# 建立資料表
$ bin/rails g migration <CREATE_tableName> <column_name: column_type> <column_name: column_type>
# 新增移除欄位
$ bin/rails g migration <ADD_columnname_TO_tableName> <column_name>:<column_type>
$ bin/rails g migration <REMOVE_columnname_FROM_tableName> <column_name>:<column_type>
# 關連式資料表
$ bin/rails g migration <ADD_tableName_REF_TO_tableName> <table_name>:references
$ bin/rails g migration <CREATE_JOIN_TABLE_tableName1_tableName2> <tableName1> <tableName2>
產生一般的 migration 檔案
migration 檔案的名稱可以用大寫駝峰或小寫底線:
# bin/rails g migration <migration_file_name>
bin/rails g migration combine_items_in_cart
建立資料表(CreateXXX)
如果 migration 的檔名是以 create
開頭,例如 CreateProducts
,則會在產生的 migration 檔案中使用 create_table
:
# bin/rails g migration create_tableName <column_name: column_type> <column_name: column_type>
$ bin/rails g migration CreateProducts name:string part_number:string
使用 add_column_to_table 新增欄位
使用關鍵字 add
和 to
可以在 migration 檔中自動增加 add_column
欄位:
# bin/rails g migration <add_columnname_to_tableName> <column_name>:<column_type>
bin/rails g migration add_income_to_candidates income:integer
使用 remove_column_from_table 移除欄位
使用關鍵字 remove
和 from
可以在 migration 檔中自動增加 remove_column
欄位:
# bin/rails g migration <remove_columnname_from_tableName> <column_name>:<column_type>
bin/rails g migration remove_votes_from_candidates votes:integer
將欄位加入 index
若需要將該資料庫欄位設為 index
可以在該欄位後直接加上 :index
,則在該 migration 檔中會自動加上 add_index
:
$ bin/rails g migration AddPartNumberToProducts part_number:string:index
使用 add_table_ref_to_table 將不同資料表關連
檔名為 Add_tableName_ref_to_tableName
搭配關鍵字 references
或 belongs_to
可以將不同資料表間的欄位互相關連,例如 AddUserRefToProducts
,將會在 products 資料表中多出一個 user_id 的欄位:
# bin/rails g migration <ADD_tableName_REF_TO_tableName> <table_name>:references
$ bin/rails g migration AddUserRefToProducts user:references
使用 create_join_table_tableName_tableName
使用關鍵字 join_table
可以建立關連性資料表
# bin/rails g migration CreateJoinTableTable1Table2 table1 table2
$ bin/rails g migration CreateJoinTableProductProductItem product product_item
操作 migration 檔
bin/rails db:create # 依照目前的 RAILS_ENV 環境建立資料庫
bin/rails db:create:all # 建立所有環境的資料庫
bin/rails db:drop # 依照目前的 RAILS_ENV 環境刪除資料庫
bin/rails db:drop:all # 刪除所有環境的資料庫
bin/rails db:migrate # 將 migration 檔產成實際的資料庫檔案
bin/rails db:migrate:status # 檢視哪些 migration 被執行過(up 已執行/down 未執行)
bin/rails db:rollback
bin/rails db:rollback STEP=1
bin/rails db:seed # 把種子資料寫入資料庫中
bin/rails db:setup # 同時執行 migrate 和 seed 指令
bin/rails db:reset # 把 db 重置(drop + setup)
因為 Rollback 通常會造成刪除資料表或是刪除欄位的效果,所以如果原本該資料表或該欄位已經有資料的話,請儘量不要使用 Rollback 方式來修正 Migration,建議直接再新增一個 Migration 來進行修正。
migration 檔內容
CheatSheet
# function <:table_names> <options> <Block[t]>
create_table <:table_names> <Block[t]>
drop_table <:table_names> <Block[t]>
add_column <:table_name>, <:column_name>, <:column_type>, <options>
remove_column <:table_name>, <:column_name>, <:column_type>, <options>
change_table
change_column
change_column_null
change_column_default
add_index <:table_name>, <:column_name>
add_reference <:table_name>, <:table_name>, <options>
add_foreign_key <:table_name> <:table_name>
create_join_table <:table_name>, <:table_name> <block[t]>
# property in t, t.<property> <:column_name> <Hash>
# t.<column_type> :<column_name>, <options>
t.string <:column_name> <options>
t.decimal <:column_name> <options>
t.references <:column_name> <options>
t.timestamps <:column_name> <options>
Table Methods
建立資料表 create_table
CreateStores
是用 generator 下 migration 指令時的檔名create_table
表示要新增 table,這裡 table 的名稱為 candidates- 如果不想要使用主鍵,可以加上
id: false
- 用
t.<type> :<column>, <options>
建立資料表中的欄位
- 如果不想要使用主鍵,可以加上
# $ bin/rails g migration createStores title tel address
# create_table <table_name>, <options>, block(<t>)
class CreateStores < ActiveRecord::Migration[5.1]
def change
create_table :stores do |t|
t.string :title
t.string :tel
t.string :address
end
end
end
修改資料表(add_column, remove_column, change_column)
add_column
- 使用
add_column
來新增欄位
# $ bin/rails g migration add_name_to_stores name:string
# add_column <:table_name>, <:column_name>, <:column_type>, <options>
class AddNameToStores < ActiveRecord::Migration[5.1]
def change
add_column :stores, :name, :string, {comment: '店名'}
end
end
remove_column
- 使用
remove_column
來移除資料表欄位
# $ bin/rails g migration remove_tel_from_users tel:string
# remove_column <:table_name>, <:column_name>, <:column_type>, <options>
class RemoveTelFromUsers < ActiveRecord::Migration[5.1]
def change
remove_column :users, :tel, :string
end
end
change_table
- 使用
change_table
來修改建立好的欄位
change_table :products do |t|
t.remove :description, :name
t.string :part_number
t.index :part_number
t.rename :upccode, :upc_code
end
change_column
* 使用 change_column
來修改特定的欄位
# change_column <:table_name>, <:col_name>, <:col_type>
change_column :products, :part_number, :text
# change_column_null <:table_name>, <:col_name>, <Boolean>
change_column_null :products, :name, false
# change_column_default <:table_name>, <:col_name>, {from: xxx, to: ooo}
change_column_default :products, :approved, from: true, to: false