- user has_many educations
- education belongs_to school
而且 educations 和 schools 資料表中各有 type 和 stage 欄位記錄這個 學歷/學校 是屬於哪個階段 (ex. 國中、高中、大學)。(題外話,type 在 Rails 中是個欄位的保留名稱,專門給 STI 使用)
在這樣的情況下,當我使用
user.educations
取得使用者學歷後,還需要再進行 Array.detect
來找對應的階段來顯示「國中讀哪裡」、「高中讀哪裡」,這樣的邏輯實作出來相當冗餘也不好看。用 STI 來讓多型別的 relation 更直觀
於是我便將架構做一點改變,新增幾個 education 的 sub-class,放在 "education" 的 NameSpace 下避免與既有的 model 撞名。並將 stage 改為 type,以建立 STI 的從屬關係,例如:
class Education::HighSchool < Education ... end
這樣一來,我們只要再建立以下的 relation,就可以使用 user.high_school 和 user.junior_high_school 直接拿到對應學歷,相當直覺:
- has_one :junior_high_school, :class_name=>"Education::JuniorHighSchool"
- has_one :high_school, :class_name=>"Education::HighSchool"
STI name hack
但在這樣的情況下,因為 Rails 的 convention,他會預設抓取 type 為 "Education::JuniorHighSchool" 或 "Education::HighSchool" 的 education。但是這樣很醜,並不直覺。我希望在type中顯示 "JuniorHighSchool" 或 "HighSchool" 就好。這時我們可以在他們的父類別,也就是 education.rb 加入以下程式碼來 hack :
class Education < ActiveRecord::Base
...
class << self
def find_sti_class(type_name)
("Education::"+type_name).constantize()
end
def sti_name
name.demodulize
end
end
end
find_sti_class 的用途是:在 table 中找到一個 entry 時,決定用什麼 model 來表達他,而傳入的 type_name 當然就是 type 欄位中的資料了。當我們如範例程式碼重寫 find_sti_class 後,如果傳入的 type_name 為 「HighSchool」,find_sti_class 會返回
Education::HighSchool
這個Class。sti_name 則是在 new 一個繼承 STI 的物件時,將 return 的值塞進 type 欄位。經過範例程式法的改寫後,新增一筆
Education::HighSchool
資料時,Rails 會在type欄位中存入經過 demodulize 的字串「HighSchool」,而不是 Education::HighSchool
。這樣一來 Rails 就會乖乖找type為 "HighSchool" 的
Education::HighSchool
model了。