前言
Single Table Inheritance(以下簡稱 STI) 是我們在 Rails 常用的一個功能,一般來說 STI 在 Rails 的實現方式如下:
- 一張資料表
- 多個 ActiveRecord Class 使用這張表
- 預設用 type 這個欄位來界定這筆紀錄所屬的 class
這樣做的好處是子類別可以用父類別的欄位,主要的缺點是在可能會浪費到不需要的欄位;Rails 之所以這樣設計,主要是因為大部份的 RDBMS 系統除了 PostgreSQL 和 Oracle 以外幾乎都沒有實作 Table Inheritance。
PostgreSQL 的 Table Inheritance
PostgreSQL 的 Table Inheritance 和 STI 最大的差別在於多表繼承,在官網上就有相當詳細的介紹,基本上就是在資料表之間實作出繼承的關係,B 表繼承 A 表的話,則:
- B 表會有所有 A 表的欄位資訊
- 即使日後 A 表欄位有所變動,也會即時反應到 B 表上
- B 表所建立的資料,在查尋 A 表時會全部出現,反之則無
接下來我們就來介紹如何在 Rails 中用 PostgreSQL 的 Table Inheritance 來實作 ActiveRecord 的物件繼承。
期望的 Schema
實作
建立父類別 User
1
|
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
建立 sub-class Staff
1
|
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
和原本 STI 的行為不同,由於 ActiveRecord Migration 並沒有內建相關功能,所以需要自行撰寫 SQL 式來建立 Staffs 表,在建立時需實際指定繼承自 users 表,並且將額外附加的欄位等屬性設定進去即可。
在 Model 的程式方面,必需指定子類別的資料表,否則 Rails 會按照 STI 預設行為去使用父類別的資料表(users)。
在這個範例中,在 staffs 表建立的資料都會出現在 users 表上,反之則不會。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Table Inheritance 會繼承的東西:
- 欄位資訊:包括預設值或是 NOT NULL,也就是在 SQL 建構式中看到欄位敘述的內容。
- 父表上的欄位變動:也就是之後在父表上新增刪除修改欄位的變動,會確實的反應到子表上。
Table Inheritance 不會繼承的東西:
- 欄位資訊以外的全部:包括 Primary Key、Constraint 或是 Index 等。
主鍵重複值問題
這應該是最大的困擾之一,就是繼承的子表的主鍵是可以和父表的值重複的,例如以下的程式碼是可以正確執行的:
1 2 |
|
這樣在 users 表會真的有兩筆 id 為 1 的紀錄,而且目前無法用資料庫的方式去迴避這個問題,不過由於兩個 id 之間是共用同一個 Sequence,在不指定 id 建立資料的狀況下,id 的值是不會重複的。
inheritance_column 存廢問題:
在上面的範例中,我們可以看到原本 STI 的 inheritance_column,也就是 type 這個欄位的存在,原本 STI 的設計是:子類別的資料將會自動在 type 上加上類別名稱,查詢 Staff 時也會預設查詢 type 為 Staff 的資料。
這個設計是因為在資料同屬一張表的前提下,必需用一個欄位去區分類別的關係,而在 Table Inheritance 下,由於每個類別有獨立的資料表,在查詢子類別時已經不需要用 type 這個條件了;但是如果在父類別的資料表查詢時,還是需要用 inheritance_column 去區分出這筆資料所屬的類別,否則所有父類別資料表的資料在 ActiveRecord 中會被視為父類別的資料。
多表繼承
其實是有這項功能的,但是目前還沒想到需要使用的場合所以這次就不討論了。
是否要使用這個功能?
好處
- 比起 ActiveRecord 提供的 STI 更趨近於物件化
- 由於資料表是物理上的切分,等於是一種 Partitioning,在資料量大的時候可以提升子表的效能
- 同上,減少欄位或 Index 等資源不必要的浪費
壞處
- Rails 預設不支援相關的 Migration 操作,需手動撰寫 SQL
- 同上,在 Model 的設定上必需客製一些設定
以上!