RESTful 與 MVC 的旅程 之 那些年我不懂的 Rails

Fred Hung
開發者特攻隊
Published in
13 min readNov 17, 2017

從開始自學Rails到現在大概快兩年了,直到最近參加ALPHA camp的課程才慢慢搞懂一些東西,整理一下從以前到現在卡關的Rails基礎概念。

以下範例皆取自 ALPHA camp 所使用的 photo album 專案,主要為基本的CRUD程式建立,有興趣看我的版本可到github參考:https://github.com/spreered/album-of-spreered

先了解了HTTP的Request Method

當我們使用http對url做連線時,client 會對 server 送出請求(Request),而請求的方法就會包在Request Header中送給server端

GET /home.html HTTP/1.1Host: developer.mozilla.org.....

而最前面GET /home.html HTTP/1.1這裡就是對於/home.html這個資源(resource 或者你要說網址)送出GET的動作,Sever端就會依照你的需求來回應。

使用chorme打開開發者模式,點選network標籤,就可以看到我們連線到一個網站的時候會對哪些資源送出哪些請求,以及Server對於該請求的回應Respons。

我們不是只有GET一種request method,所有的method可以參考 MDN:HTTP Request Method。而基本Rails CRUD程式裡面會用到的就是GET、POST、PUT、PATCH、DELET。我們可以把這個Method想像成是Client要對那個資源所行使的「動作」,所以會有人稱它為動詞verbs。
對於這幾個動詞的詳細介紹,可以參考ihower大大的網誌 HTTP Verbs: 談 POST, PUT 和 PATCH 的應用

Rails裡面MVC各自怎麼運作

Rails為採用Ruby語言實踐MVC的框架,是Model、View、Controller三個元件組成,簡單的說就是Model負責處理資料庫的部分,View是處理呈現的部分(吐出的html等等前端的東西),而Controller就是負責接收聯繫處理傳遞工作。好像略懂略懂,以下再仔細說明一下各自運作方式,第一時間看不懂的可以略過….

Model

  1. 操作資料庫,要用SQL對database下達指令。
  2. 而Rails使用Active Record來實作Model的部分:一個用物件導向來包裝所有要對database操作的指令。也就是說在Ruby語法上,Model看起來就是一個物件,一樣可以做物件的實例化和操作(比如說photo = Photo.new)。
  3. 雖然Active Record看似個物件,他把決大部分對資料庫操作的SQL指令包裝在裡面,因此我們再處理資料的時候,不太需要撰寫SQL指令。
  4. 在Model的命名我們是採用慣例,所以在本程式處理的資料命名為photo,則model的物件就叫做Photo。
  5. 我們產生的Model Class 叫做Photo,他就是繼承Active Record,可以查看app/model/photo.rb來確認。
Photo class繼承自ApplicationRecord

簡單的說:Model負責跟資料庫打交道,這個人我們通常叫他Active Record,他在我們的程式裡會長得像是Photo,而Controller其中之一的工作就是負責使喚Active Record。

View

  1. 也叫Action View,他是負責展現吐到client端瀏覽器上的那些html等等。
  2. app/views/photos/裡面,那一堆.html.erb檔就是view。
  3. ERB就是可以在html中嵌入Ruby語法的模板處理器….所以在html.erb中 <% %>這個標籤裡面的都是Ruby語法。
  4. helper這個東西是一個可愛的小幫手,本質上是一個Ruby函式(method),實際上作用就是讓你不要寫那麼多html,比如form_forlink_to這種東西都是helper。

簡單的說:View這裡會想辦法將Controller丟給他的資料呈現為美美的HTML檔案,而且可以在HTML裡面塞一些Ruby語法來處理如何呈現。

Controller

  1. 也叫Action Controller,負責接受他(接受routes指派的任務然後指派給Controller對應的Action)、處理他(請Model處理一下該處理的事)、放下他(處理後的資料丟給View去展現)。
  2. routes.rb裡面就是所有的路由,可以想像成「誰上門來我就要丟給哪個Action的一張表」。而我們在其中撰寫resources :photos,他就會自動產生7條常用的路徑表。
  3. 在controller裡面使用都@photo實例變數而不使用區域變數的原因就是為了這個變數可以給view使用。
  4. 在每個Action 裡面,Rails最後都會偷偷加上 render “跟自己同名的view”,因此如果不是特別要幹嘛,Action最後都會去找跟自己同名的那個.html.erb檔。比如說Controller中的index action預設會去找(render) index.html.erb
  5. photo的命名單複數是慣例!很多就叫photos,一個就叫photo,他就是Rails的慣例,前面講到的routes自動產生的路徑也是依照慣例產生。
resource :photos 自動產生的路由

簡單的說:Controller在Action中 把資料叫出來(如 @photo=Photo.new),然後預設這個資料會吐給跟自己同名的View (.html.erb)。

Rails 裡RESTful怎麼運作

  1. 使用者對某資源使用某動作,Rails就會由routes判斷這件事情要交由誰處理。比如說使用者對http://myweb.com/photos送出GET請求,就會丟給photos Controller裡面的index action(請參考上面的路徑表)。
  2. 那使用者怎麼下達這些動作:當然是透過view來撰寫這些動作。
  3. (對照上表)view透過prefix來代表這些網址,只要prefix加上’_path’,在view裡面就是一個變成網址的helper。比如說在html.erb撰寫photos_path,就會變成http://yourweb.com/photos/這個網址。
  4. 而像是GET、POST、UPDATE等HTTP Verb,一樣也要在view裡面表達清楚,只是Rails很貼心的,很多時候Verb不用加,他會自己幫生預設的verb出來。

所以使用者就可以透過Rails View呈現的網頁,點選頁面上的連結或按鈕,產生相對應的功能。

view裡面撰寫相對應的action

使用者看到的是view呈現後的畫面,點那些連結就可以使喚Rails。

使用者會看到的頁面

RESTful 和 MVC的旅程

這裡用連接到photos/為範例,大致上從使用者送出Request,到Server端回傳的順序是這樣:

而詳細一點來說細節是這樣:

  1. 使用者在瀏覽器網址列輸入http://myweb.com/photos/送出,這預設就是送出GET的動作。
  2. Server端收到請求後,會判斷GET photos/ 這個請求是由photos#index負責,所以去找photos controller中的index action
  3. index action裡面,他會請Photo這個model,使用Photo.all的操作,將所有資料庫裡面的資料撈出來,存在@photos的變數裡。
  4. index action 預設會去render index template,換句話說就是預設會去找同名的view :index.html.erb,然後將@photos給他處理呈現。
  5. index.html.erb 會使用@photos裡面存的資料,使用迴圈列出所有的照片,並且使用helper加上一些其他頁面的功能。最後樣版引擎會處理後,產為一般瀏覽器可接受的html和css等其他檔案,然後送回使用者的瀏覽器呈現。

其他我終於搞懂的事情

“:” 到底要放在哪裡 — Symbol

剛開始撰寫Rails的時候很苦惱的一件事情就是,冒號到底要放在哪?

如果遇到冒號,其中有87%都是Symbol。Symbol在Rails中被大量的使用的原因,就是同一個Symbol會使用同一個記憶體區塊,比如使Symbol :myname 和字串"myname"的差別就在每次使用字串"myname"都會重新產生一個字串物件,以效率來說是比較低的。因此Symbol這種特性很適合拿來當Hash中的key值。而要注意的是使用Symbol當key會有兩種寫法:

profile = {:name => "Fred", :age => 30}

可以寫成

profile = {name: "Fred", age: 30}

這樣就知道冒號會擺哪裡了。

“@”到底要放在哪裡 — 實例變數

@變數就是實例變數。區域變數是離開method就會不見,而實例變數就是在物件實例化後可以跨越method被使用的變數,也就是如果需要丟去給Action View作用的變數,一定要宣告為實例變數。

def index  @photos = Photo.allend

Rails很愛使用Hash

在參數傳遞的部分Rails很愛使用HASH,因此可以常常看到helper裡面出現類似method: :delete"這種使用symbol作為key-value的寫法。另外再view傳遞給controller裡面,也使用params這個HASH傳遞參數。
關於params可參考:Rails 當中的 params 是什麼?

新增new和編輯edit怎麼運作,form_for helper又偷偷做了什麼事情?

新增照片的流程來說:

  1. 我們要新增時,會對server送出GET new_photo_path這個動作。
  2. 然後new action會導向到/photos/new這個頁面。
  3. 當我們填寫完送出表單(submit),則對photos_path送出POST動作,所以route會判斷交給create action處理。
  4. create action裡面只處理資料的儲存,因此沒特別去撰寫一個create.html.erb。
  5. 因為沒有create.html.erb,所以在create action最後要讓他知道網頁要送去哪裡:使用redirect_to photos_path回到首頁。
    (關於model的validate部分這裡就略過沒有提)

所以我們在new.html.erb的頁面,使用form_for這個helper來產生html的 <form> 標籤。如果再html中什麼方法都沒指定的話,submit後預設是送出“GET”這個動作。

(可以參考:https://www.w3.org/TR/html401/interact/forms.html

因此必須在form標籤裡面指名: (1)要用什麼 HTTP method (2)要作用在哪個url 。寫出來的html長得會像這樣

<form action="http://這裡是你要送出動作的網址.com" method="post">

因此我們的form_for helper開頭應該要寫成

form_for @photo ,  :url=>photos_path ,method: :post do |f|...

這個這helper最後會變成下面這段HTML

<form class="new_photo" id="new_photo" action="/photos" accept-charset="UTF-8" method="post">

但是form_for helper很貼心的,知道如果你是在new頁面下,你只要打出 form_for @photo do |f|他就可以自動生出POST method。

而edit的運作和new很像,先到photos/:id/edit 頁面去,submmit後就對photos/:id送出UPDATE動作,然後更新資料庫,因此我的helper應該寫成:

form_for @photo ,  :url=>photo_path(@photo) ,method: :patch do |f|
...

然後會變成下面的html

<form class="edit_photo" id="edit_photo_1" enctype="multipart/form-data" action="/photos/1" method="post">

helper很貼心的,只要你在edit.html.erb中只要寫 from_for @photo do |f| ,他就會幫你自動生成上面的這段。

另外還有個問題,不是應該送update action嗎?為什麼這裡看起來是POST 因為html裡面<form>的method只吃GET和POST,如果要使用UPDATE verb,就要在下面偷加一行<input type="hidden" name="_method" value="patch">,而這個form_for也貼心的幫你塞下去了,使用開發者模式就可以比照出.erb原檔和rails真正render出來html的結果。

因此new.html.erb和edit.html.erb中的form_for部分,就可以完整抽離出來,變成一個partial template:

--

--

Fred Hung
開發者特攻隊

當過吉他老師和公務員,現在努力當個 1 x Web 工程師。