前情提要:
由於不太清楚自連結要怎麼設定,就算看完官方手冊,還是不知道他在講啥,因此打算以自己的方式解釋他的運作原理

1、自連結 Self-Join

雖然看了官方文件,還是不了解,但是還是先從官方文件做起手,所以我先把官網的原文放上來並附上翻譯
官方文件

1-2、基本概念


英文原文
In designing a data model, you will sometimes find a model that should have a relation to itself. For example, you may want > to store all employees in a single database model, but be able to trace
relationships such as between manager and > subordinates. This situation can be modeled with self-joining associations:

翻譯
在設計model的時候,你有時候會發現一個model應該要與自身欄位有關聯。
舉個例子,你今天想要把所有員工存在一個資料表中,但是你想要這張表可以追蹤上司、下屬間的關係
這種情況就可以使用到自連結

1-2、Model關聯

接著到Employee的model來設定一些關聯

> class Employee < ApplicationRecord
>   has_many :subordinates, class_name: "Employee",
>                           foreign_key: "manager_id"
> 
>   belongs_to :manager, class_name: "Employee", optional: true
> end


英文原文
With this setup, you can retrieve @employee.subordinates and @employee.manager.
In your migrations/schema, you will add a references column to the model itself.

翻譯
經由剛剛上面那樣設定,我們可以得到下面兩個方法,取得一些資料
(1) @employee.subordinates
(2) @employee.manager
並且在你的migration、schema中,你將會在自己的欄位中,加上一個檢索值

1-3、Migration內容

再來處裡migration

> class CreateEmployees < ActiveRecord::Migration[7.0]
>   def change
>     create_table :employees do |t|
>       t.references :manager, foreign_key: { to_table: :employees }
>       t.timestamps
>     end
>   end
> end

也因為官方文件這只有說到這,所以一開始看完覺的滿頭霧水,因此下面會用自己的方式解釋Self-Join


2、換成自己的話解釋Self-join

範例一樣用員工、上司、下屬,三者的關係來解釋自連結

2-1、基本概念詳解

首先我有一家公司,而我這一家公司的員工所有資料都在一張Employee表裡面,而公司理所當然的都有管理層、基層員工…..等等的分別
因此當我把員工所有資料都放在這張表單的時候,我想要知道到底誰是管理層,誰是基層員工呢?

這邊有兩個情境,來解釋員工中的關係從屬
(1) 一名員工(Employee),會向另外一名員工(Employee)報告,後面這個聽報告的員工,另外一個身份就是主管(manager)
(2) 一個員工(Employee),會監督其他的員工(Employee),後面這個被監督的員工,另外一個身份就是下屬(subordinates)


自連結其實就是同一張表內,自己的Primary Key,會被當作其他人FOREIGN KEY
舉個例子,假設我今天有一個員工(Employee),他的id是20051,接著我有另外一個員工,他的id是20073,這個員工有一個上司,他上司的
ID就是剛剛前一號員工,因此這一張表有另一個欄位叫做EMP_SUPV,後面那個員工EMP_SUPV欄位的值,就是20051

如果看不懂上面那段的話,這邊有一張圖,可以了解自連結之間的關聯:圖片來源

2-2、產生Model

首先創造一個Employee的model,裡面會有一個name欄位,代表這個員工的名字

> rails g model Employee name

2-2、更新migration

接著到migration加上manage_id的欄位,這個欄位就是之後要存放每一個員工的主管是誰(用Employee的id來表示)

> def change
>   create_table :employees do |t|
>     t.string :name
>     t.references :manager, foreign_key: { to_table: :employees }       # 就是這一行!!
>     t.timestamps
>   end
> end

2-3、具現化

新增好後就可以把表格具現化了

> rails db:migrate

2-4、Model寫關聯

再來可以到model把關聯寫上去了,這邊會有兩個主要的關聯,一個是subordinates、一個是manager

> class Employee < ApplicationRecord

>   // 一個員工有很多下屬,對到的表是自己這張表 -> 使用的外鍵是manager_id (存放資料下屬id的地方)
>   has_many :subordinates, class_name: "Employee", foreign_key: "manager_id"
 
>   // 一個員工會有一個老闆,對到的表是自己這張表   
>   belongs_to :manager, class_name: "Employee", optional: true  

> end

2-5、console測試、創造多個員工

那實際應用的狀況是怎麼樣呢?我們來在console的環境跑一次。
進到環境後,我們先創造出五個員工

> rails c

> e1 = Employee.create(name: "魯夫")
> e2 = Employee.create(name: "索隆")
> e3 = Employee.create(name: "那美")
> e4 = Employee.create(name: "喬巴")
> e5 = Employee.create(name: "羅賓")

2-6、測試subordinates方法

先來試試看subordinates這個方法,此方法其實代表的就是,如果今天e1是主管,那我可以用這方法,找他的下屬有哪些人

> e1.subordinates               # 這樣打會傳一個空陣列給你,原因是因為我們還沒把下屬給e1主管

> e1.subordinates << e2         # 我把e2員工指定給e1,這樣的意思就是,"索隆"的主管是"魯夫"
> e1.subordinates << [e3, e4]   # 這個意思是我要一次指定e3、e4這兩位員工,當作e1的主管,也就是那美、喬巴的主管都是魯夫

2-6、測試manage方法

再來試試manager的方法,此方法代表的則是,如果我今天是一個員工,我要指定我的老闆是誰(跟剛剛的邏輯相反)

> e5.manage = e1
> e5.save

> 這的意思是,e5的老闆是e1,也就是羅賓的老闆是魯夫,在羅賓的manage_id那邊,顯示的id就會是魯夫的id

3、心得結論

翻了其他英文文章、自己實際動手寫關聯後,才終於了解self-join的強大之處,因為可以直接在同一張表上,就讓資料有階層的關係,查找起來也非常的方便。