跳至主要内容

[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 新增欄位

使用關鍵字 addto 可以在 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 移除欄位

使用關鍵字 removefrom 可以在 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 搭配關鍵字 referencesbelongs_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_idproduct_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_atupdated_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 :usert.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: Boolean
  • index:Boolean,為該欄位加上索引

建立種子資料(seed)

db/seeds.rb 中,一樣可以撰寫 migration 檔,和 migration 檔不一樣的地方在於,seeds.rb 是可以重複執行的。

# 寫在 db/seeds.rb 中
Article.create(title: "五倍紅寶石 part 1", content: "斷開鎖鍊吧!")
Article.create(title: "五倍紅寶石 part 2", content: "斷開魂結吧!")

參考資料