[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
關連式資料表(create_join_table, add_reference)
create_join_table
- 使用
create_join_table
將產生一個 table ,它會包含另外兩個 table 的 id 來建立關連式資料表,這是一個 HABTM (has and belongs to many) 的結構。 - 預設
:null
會是 false,如果需要可以透過:column_options
來修改 - table 的名稱預設會按照字母順序加給入的兩個 table_name 合起來,若想修改可以透過
table_name:
來修改。 - 會建立一個名為
product_items_products
的資料表,裡面包含product_id
和product_item_id
這兩個欄位。
# $ bin/rails g migration CreateJoinTableProductProductItem product product_item
# create_join_table <:table_name>, <:table_name> <block[t]>
class CreateJoinTableProductProductItem < ActiveRecord::Migration[5.1]
def change
create_join_table :products, :product_items, column_options: {null: true}, table_name: :categorization do |t|
# t.index [:product_id, :product_item_id]
# t.index [:product_item_id, :product_id]
end
end
end
add_reference
- 使用
add_reference
將在原本的 table 中增加另一個 table 的 id 來建立關連式資料表。 add_users_ref_to_stores
執行完後,stores
資料表會多一個users_id
的欄位,並且多一個名為index_stores_on_users_id
的 index。
# $ bin/rails g migration AddUsersRefToStores users:references
# add_reference <:target_table>, <:ref_table>, <options>
class AddUsersRefToStores < ActiveRecord::Migration[5.1]
def change
add_reference :stores, :users, foreign_key: true
end
end
可以想成 前面(target_table) 會多一個 (ref_table)的 id。
建立索引(add_index)
# add_index <:table_name>, <:column_name>
add_index :users, :candidates
add_index :users, :email, unique: true
add_index :roles, [:name, :resource_type, :resource_id]
Table Modifiers
id: false
:不要產生 primary key:primary_key
:修改 primary key 預設的名稱:options
:放入的特定的 SQL 指令:comment
:關於此資料表的註解:column_options
: Hash,要放入 column 的屬性和值:table_name
: Symbol,放入資料表名稱,例如(:categorizations)
# ./db/migrate/migration_files.rb
# 透過 id: false 來移除主鍵
create_table :assemblies_parts, id: false do |t|
end
Column type (t)
在 block|t| 中可以使用的屬性包括
t.string
t.integer
t.text
t.decimal
--
t.datetime
t.timestamp
:自動產生created_at
和updated_at
欄位
--
t.remove
: Symbol,要移除的欄位t.index
: Symbol,要加入索引的欄位t.rename
:Symbol,要重新命名的欄位(t.rename :upccode, :upc_code
)t.belongs_to
:Symbol(t.belongs_to :category, type: :uuid, foreign_key: true
)t.references
: 表示它會參照到另一個 table 的uuid
,例如t.references :user, foreign_key: true
)時該資料表會多一個user_id
。
過去在關連資料庫建立 migration 檔案時,會直接使用
t.integer :user_id
,也可以使用t.references :user
或t.belongs_to :user
,這三個實際做的內容是一樣的,只是語意上不同而已。都會在該 table 中多出一個 user_id 的欄位。
Column Modifiers
可以使用的 options 包括:
comment
: String,用來描述資料欄位。null
: Boolean,是否允許空值。default
: String,設定預設值。limit
: Number,限制 maxSize(string/text/binary/integer)precision
: Number(decimal)scale
: Number(decimal)polymorphic
: Boolean,在 belongs_to 後加上一個 column_type。
t.references :imageable, polymorphic: true, type: :uuid, index: true
type
::uuid
foreign_key
: Booleanindex
:Boolean,為該欄位加上索引
建立種子資料(seed)
在 db/seeds.rb
中,一樣可以撰寫 migration 檔,和 migration 檔不一樣的地方在於,seeds.rb 是可以重複執行的。
# 寫在 db/seeds.rb 中
Article.create(title: "五倍紅寶石 part 1", content: "斷開鎖鍊吧!")
Article.create(title: "五倍紅寶石 part 2", content: "斷開魂結吧!")
參考資料
- Active Record Migrations @ RailsGuides
- Active Record - 資料庫遷移(Migration) @ ihower
- Model Migration @ 為你自己學 Ruby on Rails