Rails 撞牆期新手的破關指南(下)

「MVC可以保護眼睛」、「MVC可以鞏固牙齒」、「好的MVC架構帶你上天堂,不好的軟體架構帶你住套房」。「MVC」是 Rails 開發者在學習階段頭幾個會接觸到的術語,接觸到這個名詞後,各式各樣的「MVC傳說」也會接踵而來。

MVC 就像失傳已久、沒人見過真面目的武林絕學,大家都迫不及待豎起大拇指說一聲好,唯恐自己不說 MVC 的好會被笑外行。但 MVC 的到底是什麼,卻似乎百家爭鳴,每家的解釋、使用方式都有點不一樣。「逾八成開發者支持 MVC 架構,但細節不公開」(姊妹文章:
)。

在我的 Rails 生涯中,戰戰兢兢檢視自己的 code 有沒有符合 MVC 規範,可是花了不少精力。但MVC到底是什麼,各方定義卻莫衷一是,真是苦了當年初學 Rails 的我。

被 「MVC」搞得無所適從絕對不是學習者的問題,因為 MVC 真的是「一個 MVC 各自表述」、沒有統一定義的理論。(熱情推薦:MVC是一個巨大誤會)

且不論各門各派的高手怎麼表述 MVC,我自己倒是在寫了 Rails 一段時間後琢磨出屬於自己的 MVC 表述。

一個 Rails APP 無非就是「根據使用者的需求處理資料」的軟體。MVC 中的 View 站在第一線和使用者互動,呈現資料給使用者或是接收使用者傳來的資料;Controller 根據使用者的需求做一些基本的邏輯處理; Model 在最底層和資料庫打交道、處理資料。

在「一個 MVC 各自表述」的狀況下,這套邏輯在其他 framework 未必完全正確,但在 Rails 的架構下這樣理解 MVC 卻無啥大礙。

Page-heading

View 的主要功能是呈現資料給使用者看(在 Restful 中便是 show 和 index)及讓使用者輸入資料( new 及 edit )。

重要的任務是透過排版與設計,讓使用者好讀、不困惑,基本上不直接處理程式邏輯。

除了部分要透過簡單程式碼呈現資料的狀況(例如用 each 迭代呈現 array 裡面的資料)和簡單的防呆機制,在 View 中有程式碼,十之八九是有問題的。(複雜的防呆機制通常用 javascript 處理,不會寫在 View 裡面)

Controller

Controller 是 View 和 Model 的中間人,根據 Restful 的規則接收使用者的需求,並在簡單的邏輯處理後,把透過 Model 取得的資料呈現給使用者(index 或 show);或是把透過 View 拿到的資料傳給 Model ( create 或 update)。

假設我們要建立一個社交網站,個人頁面及編輯個人頁面是基本的功能。

使用者若想在這個社交網站上關注習近平總書記,知道習總書記在世紀之握後是否身體健康,只要透過 Restful 的網址規則訪問屬於習總書記的頁面, Controller 便會去問 Model 習總書記在資料庫中的資料,並將資料傳遞給 View,讓關心習總書記的朋友瀏覽。在這個例子中, Controller 只是居中傳遞資訊,幾乎沒有功能。不過可別因為這個例子就幫 Controller 貼上「冗員」的標籤。

若有頑皮的使用者試圖竄改習總書記的健康狀態,盡責的 controller 會檢查修改資料的人是否有權限替習總書記修改健康狀態,沒有的話則直接把這件事擋住,不讓頑皮的使用者真的把資料改掉;若修改者確實有權限, Controller 也要對使用者傳的資料做一些驗證,擋掉有問題的資料(透過 strong parameter),權限和資料都沒問題才通知 Model 修改。在這個例子中, Controller 就搖身一變成為重要的守門員了(實務上確實會有工程師疏忽,沒在 controller 中做好檢查,讓使用者可以做理論上沒權限或不應該做的事)。

最常見的狀況是,網站會根據使用者登入與否呈現不同資料,判斷使用者是否登入的重責大任一般也是由 controller 擔負。

簡單的說, Controller 是使用者和 Model 的中間人,在使用者觸碰到資料前,做一些安全、權限上的處理,或是事先處理一些邏輯,盡可能減少資料庫的負擔。

Page-heading

在將使用者的資料存入資料庫,或是將使用者需要的資料從資料庫取出呈現前,我們常常需要做一些處理,因為適合使用者輸入或閱讀的資料格式,不一定適合資料庫儲存。 Model 的重要使命就是在「適合使用」和「適合儲存」之間轉換資料。

例如一般資料庫的設計,姓 (last_name)和名(first_name)是分開儲存的,可是畫面上又常常需要用全名(full_name),這時實務上最常看到的解法就是在 Model 中建立一個 method 負責回傳 last_name + first_name (支援多國語系的話,還要判斷姓要放前面或後面)。

勇於任事的Model

Controller

View 的分工相對很明確,通常不會造成困惑,但 Model 和 Controller 之間的分工有時候就有些曖昧了。

例如我們要在社交網站上呈現最近一個月和馬總統握過手的 10 名使用者,我們可以直接在 Controller query,也可以把 query 放在 Model 包成一個 method。

一般遇到這種難以判斷的任務,大原則是 “Fatmodels and skinny controllers“,也就是盡可能把東西都放到 model、讓 controller 盡可能精簡。對這個原則有興趣的讀者可以點入連結,裡面有不少討論。

我自己覺得把邏輯盡可能的放入 Model , 一個明顯且直接的好處是要重複使用時方便很多,你可以輕易的在 A、B 兩個 Controllers 同時使用 C Model 中的 method,但要在 A Controller 使用 B Controller 的 method 在 Rails 的架構下就既不方便又很奇怪。

結論

MVC 的大原則如下:

  • View: 僅呈現、接收資料,不處理任何程式邏輯
  • Controller: 在使用者和 Model 互動前,做些權限、安全上的處理,以及使用 Model 之前的前置作業
  • Model:在「適合使用」和「適合儲存」之間轉換資料、和資料庫互動

MVC 是個沒有精確、統一定義的理論,本篇僅為我自己的開發心得,而且有很多細節限於篇幅無法細說。照著本篇表述的原則使用 MVC 不會出什麼大錯,但實務上如何使用、取捨,還要看個人體悟、團隊規範。