前情提要:
由於之前在跟助教討論專案的時候,有提到如何把Model的複雜度提高,那時候就有提到”多型”這個關鍵字
因此決定來寫一篇文章,來搞懂這個東西到底是什麼?為什麼可以提高Model的複雜度

1、多型態 Polymorphic Associations

1-1、名詞定義

要了解一個名詞是什麼,最好的方式就是從官方文件下手,所以我先把官網的原文放上來並附上翻譯
官方文件連結

英文原文
A slightly more advanced twist on associations is the polymorphic association.
With polymorphic associations, a model can belong to more than one other model, on a single association.
For example, you might have a picture model that belongs to either an employee model or a product model.
Here’s how this could be declared:

翻譯
稍微更加複雜度更高的關聯表,就是多態性的關聯。
使用多態性的關聯得時候,在單個關聯上,一個model,可以屬於其他多個model
舉個例子,你可能會有一個圖片model,而這個圖片model是屬於員工model、產品model

1-2、官網範例

實際來寫model試試,首先讓大家知道,圖片、員工、產品之間的關聯
(1) 圖片model有一個imageable欄位,並且model自己是屬於imageable,並且開啟多型態
(2) 一個員工會有很多張圖片,以imageable當作基準
(3) 一個產品會有很多張圖片,以imageable當作基準

> class Picture < ApplicationRecord
>   belongs_to :imageable, polymorphic: true
> end
> 
> class Employee < ApplicationRecord
>   has_many :pictures, as: :imageable
> end
> 
> class Product < ApplicationRecord
>   has_many :pictures, as: :imageable
> end

英文原文
You can think of a polymorphic belongs_to declaration as setting up an interface that any other model can use.
From an instance of the Employee model, you can retrieve a collection of pictures: @employee.pictures.

Similarly, you can retrieve @product.pictures.

If you have an instance of the Picture model, you can get to its parent via @picture.imageable.
To make this work, you need to declare both a foreign key column and a type column in the model
that declares the polymorphic interface:

翻譯
你可以把多型belongs_to的寫法,當作其他model都可以使用的連接處 以員工產生的實體為例,你可以在終端機這要打@employee.pictures,把所有員工的圖片拉出來

同樣的,你也可以對產品下一樣的指令@product.pictures

如果你的圖片有一個新的實體,你可以藉由輸入@picture.imageable,來獲取這個實體的父層級
為了要讓多型態啟動,你需要在設定多態連接處的model中,多設定一個外鍵(foreign key)、類別(type)

1-3、Picture table設定

接著我們來設定Picture的Migration,照片會有以下這些欄位

> class CreatePictures < ActiveRecord::Migration[7.0]
>   def change
>     create_table :pictures do |t|
>       t.string  :name                                  
>       t.bigint  :imageable_id                             -> 多設定一個外鍵,bigint是比較大數字的意思
>       t.string  :imageable_type                           -> 多設定一個type欄位
>       t.timestamps
>     end
>     
>     add_index :pictures, [:imageable_type, :imageable_id]
>   end
> end

接著我們來把上面的Migration具現化,也就是rails db:migrate
會發現imageable那個欄位,後面會有polymorphic: true

> class CreatePictures < ActiveRecord::Migration[7.0]
>   def change
>     create_table :pictures do |t|
>       t.string :name
>       t.references :imageable, polymorphic: true
>       t.timestamps
>     end
>   end
> end

1-4、官網圖片範例

我們來看看這樣設定完後,Table的關聯實際上長的樣子


2、換成自己的話解釋多型態

不過怕上面看官網的說明還是不太懂,這邊再做另一個例子。

每個消費者有很多則留言,每個商店老闆也有很多則留言,因此這時留言就同時屬於消費者和老闆的model,但是留言model又不像多對多的第三張表(第三張表是紀錄另外兩張表的關係,而留言model是單純都屬於另外兩個的model),這時候就把留言的model,當作一個公開的連接處,提供給其他的model來使用

2-1、範例解析

(1) 有一個留言的Model,自己有一個欄位是commentable,這個欄位就是當作公開的連接處
(2) 消費者有很多的留言,他就是用comment裡面的commentable來存取留言(消費者自己的欄位有一個name欄位)
(3) 老闆也是同上

2-2、Model關聯設定

> class Comment < ApplicationRecord             
>   belongs_to :commentable, polymorphic: true
> end
>  
> class Customer < ApplicationRecord
>   has_many :comments, as: :commentable     # 透過as 來串接
> end
>  
> class Manager < ApplicationRecord
>   has_many :comments, as: :commentable     # 透過as 來串接
> end

2-3、實際寫入資料庫

這樣建好關聯後,我們就可以來查找資料了

(1) 首先我想建立消費者的留言

> c1 = Customer.create(name: "魯夫")            # 先創造一個消費者
> c1.comments.create(content: "我要成為海賊王")   # Comment裡面有一個欄位是content,因此把資料存去進

> < Comment id: 1, content: "我要成為海賊王", commentable_id: 1, commentable_type: "Customer" >
> Ps. c1.comments create後,會發現Comment的model,寫進了這些資料、還有就是後面有時間標記,我省略掉了 

(2) 接著我們再來寫進老闆的留言,老闆的留言,我們這邊創造兩個實體(也就是老闆留言兩次)

> 第一則留言
> m1 = Manager.create(name: "紅髮")         # 先創造一個老闆
> m1.comments.create(content: "給個面子")    # Comment裡面有一個欄位是content,因此把資料存去進
> < Comment id: 2, content: "給個面子", commentable_id: 1, commentable_type: "Manager" >

> 第二則留言
> m2 = Manager.create(name: "雷神")         # 先創造一個老闆
> m2.comments.create(content: "給我黃金")    # Comment裡面有一個欄位是content,因此把資料存去進
> < Comment id: 3, content: "給我黃金", commentable_id: 2, commentable_type: "Manager" >

> Ps. 這樣可以發現,存在Comment資料表裡面的commentable_id,會因為type不同,而有不同的計數

前面我從消費者、老闆的角度建立留言,接下來我想要透過commentable來反查老闆、消費者都留了哪些留言

> c1 = Comment.first              # 先抓出第一則留言
> c1.commentable                  # 我要知道是誰的留言,是老闆還是消費者的

> < Customer id: 1, name: "魯夫" >

3、心得結論

看起來多型真的蠻方便的,假如今天很多表格都要放圖片,又或者是今天有很多不同的角色,都會做留言的動作,就可以透過多型的model來製作。

再來回到一開始寫這一篇文章的目的,主要是了解多型是否可以增加model的複雜度,自己理解過後,看起來是真的可以把model弄的很複雜,因為在自己的model裡面做一個外鍵,等於可以對自己的欄位做查找,如果今天有很多角色做留言的話,model的確可以變得很複雜。