從開始自學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端就會依照你的需求來回應。
我們不是只有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
- 操作資料庫,要用SQL對database下達指令。
- 而Rails使用Active Record來實作Model的部分:一個用物件導向來包裝所有要對database操作的指令。也就是說在Ruby語法上,Model看起來就是一個物件,一樣可以做物件的實例化和操作(比如說photo = Photo.new)。
- 雖然Active Record看似個物件,他把決大部分對資料庫操作的SQL指令包裝在裡面,因此我們再處理資料的時候,不太需要撰寫SQL指令。
- 在Model的命名我們是採用慣例,所以在本程式處理的資料命名為photo,則model的物件就叫做Photo。
- 我們產生的Model Class 叫做Photo,他就是繼承Active Record,可以查看
app/model/photo.rb
來確認。
簡單的說:Model負責跟資料庫打交道,這個人我們通常叫他Active Record,他在我們的程式裡會長得像是Photo,而Controller其中之一的工作就是負責使喚Active Record。
View
- 也叫Action View,他是負責展現吐到client端瀏覽器上的那些html等等。
- 在
app/views/photos/
裡面,那一堆.html.erb檔就是view。 - ERB就是可以在html中嵌入Ruby語法的模板處理器….所以在html.erb中 <% %>這個標籤裡面的都是Ruby語法。
- helper這個東西是一個可愛的小幫手,本質上是一個Ruby函式(method),實際上作用就是讓你不要寫那麼多html,比如
form_for
、link_to
這種東西都是helper。
簡單的說:View這裡會想辦法將Controller丟給他的資料呈現為美美的HTML檔案,而且可以在HTML裡面塞一些Ruby語法來處理如何呈現。
Controller
- 也叫Action Controller,負責接受他(接受routes指派的任務然後指派給Controller對應的Action)、處理他(請Model處理一下該處理的事)、放下他(處理後的資料丟給View去展現)。
- routes.rb裡面就是所有的路由,可以想像成「誰上門來我就要丟給哪個Action的一張表」。而我們在其中撰寫
resources :photos
,他就會自動產生7條常用的路徑表。 - 在controller裡面使用都@photo實例變數而不使用區域變數的原因就是為了這個變數可以給view使用。
- 在每個Action 裡面,Rails最後都會偷偷加上 render “跟自己同名的view”,因此如果不是特別要幹嘛,Action最後都會去找跟自己同名的那個.html.erb檔。比如說Controller中的index action預設會去找(render) index.html.erb
- photo的命名單複數是慣例!很多就叫photos,一個就叫photo,他就是Rails的慣例,前面講到的routes自動產生的路徑也是依照慣例產生。
簡單的說:Controller在Action中 把資料叫出來(如 @photo=Photo.new
),然後預設這個資料會吐給跟自己同名的View (.html.erb)。
Rails 裡RESTful怎麼運作
- 使用者對某資源使用某動作,Rails就會由routes判斷這件事情要交由誰處理。比如說使用者對
http://myweb.com/photos
送出GET
請求,就會丟給photos Controller裡面的index action(請參考上面的路徑表)。 - 那使用者怎麼下達這些動作:當然是透過view來撰寫這些動作。
- (對照上表)view透過prefix來代表這些網址,只要prefix加上’_path’,在view裡面就是一個變成網址的helper。比如說在html.erb撰寫
photos_path
,就會變成http://yourweb.com/photos/
這個網址。 - 而像是GET、POST、UPDATE等HTTP Verb,一樣也要在view裡面表達清楚,只是Rails很貼心的,很多時候Verb不用加,他會自己幫生預設的verb出來。
所以使用者就可以透過Rails View呈現的網頁,點選頁面上的連結或按鈕,產生相對應的功能。
使用者看到的是view呈現後的畫面,點那些連結就可以使喚Rails。
RESTful 和 MVC的旅程
這裡用連接到photos/
為範例,大致上從使用者送出Request,到Server端回傳的順序是這樣:
而詳細一點來說細節是這樣:
- 使用者在瀏覽器網址列輸入
http://myweb.com/photos/
送出,這預設就是送出GET的動作。 - Server端收到請求後,會判斷
GET photos/
這個請求是由photos#index
負責,所以去找photos controller中的index action - index action裡面,他會請Photo這個model,使用Photo.all的操作,將所有資料庫裡面的資料撈出來,存在@photos的變數裡。
- index action 預設會去render index template,換句話說就是預設會去找同名的view :index.html.erb,然後將@photos給他處理呈現。
- 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又偷偷做了什麼事情?
新增照片的流程來說:
- 我們要新增時,會對server送出GET new_photo_path這個動作。
- 然後new action會導向到/photos/new這個頁面。
- 當我們填寫完送出表單(submit),則對photos_path送出POST動作,所以route會判斷交給create action處理。
- create action裡面只處理資料的儲存,因此沒特別去撰寫一個create.html.erb。
- 因為沒有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: