巨觀學習 Rails 的 routing 路由
WriterShelf™ is a unique multiple pen name blogging and forum platform. Protect relationships and your privacy. Take your writing in new directions. ** Join WriterShelf**
WriterShelf™ is an open writing platform. The views, information and opinions in this article are those of the author.
Like
or Dislike
About the Author
很久以前就是個「寫程式的」,其實,什麼程式都不熟⋯⋯
就,這會一點點,那會一點點⋯⋯
More to explore
開發網站一個網站或是服務,簡單說,就是使用者透過網址 「URL」來與網站的伺服器「互動」,所以,網址的規劃應該就是網站開發的起點了!
網址的規劃是有規矩與習慣的
互動就好像是對話,使用者(Client 端)發出一個「URL」,網站伺服器(Server 端)做出「內容」,並回應一個新的「URL」,就這樣一來一往,例如:
事實上,網址的規劃是可以隨你喜愛,甚至亂搞的,只是,大家使用全球資訊網(World Wide Web)已經超過 30 年了,慢慢的,大家也建立了一些習慣用法,這些用法沒有絕對的好或是不好,但是,習慣總是經驗的累積,如果自己再創一套新的,不只使用者看起來怪,可能還會碰到新的問題,將來他人來維護你開發的網站也會很辛苦的,所以, 當我們在開發網站時,我們應該要先認識這些習慣用法。
model 名稱 = 網址命名
在 Rails 中,網址的規劃,就是 routing,是寫在 config/routes.rb 中,詳細的使用說明,各位還是請看這:官方文件,這篇文章沒有任何意圖想要取代官方文件,但是,我希望能以更高的角度來看網址規劃,先讀完這篇,應該會更容易了解官方文件中想要表達的是什麼,Rails 的官方文件很有趣,很多細節是寫給熟手看的。
網址要規劃?很多 Rails 的開發者可能根本都沒想過,因為 Rails 太好用了,我想很多人會選擇用 rails 來開發網站,應該都是被美麗又厲害的 rails generate「scaffold」給吸引來的,多厲害啊,就像下面這樣,一行指令,什麼都寫好了,真棒!(如果你還沒用過 Rails,rails generate scaffold 指令就只是一個 Rails 自動程式產生器而已。)
如上,「scaffold」會自動產生 model product 的 routing,就這樣,prodcuts 就被設定成網址的名稱了,很多人往往都忽略了網址是需要規劃的,急著寫 code,網址也就這樣莫名的被命名了。
這篇文章只談 REST,REST 的網址命名在英文是被稱為:REST Resource Naming,什麼是 REST?如果你是電腦科系畢業的,一定要搞懂,如果學歷不重要了,不懂也沒什麼損失,其實就是我們之前所說的 Client 端和 Server 端「互動方法」,一般來說,知道這樣就夠了,如果你還要挖下去,下面這篇可以讀讀,算介紹的不錯!
[不是工程師] 休息(REST)式架構? 寧靜式(RESTful)的Web API是現在的潮流? — 好拉,其實這笑話一點也不好笑,本文是要介紹常見的五種HTTP Method
進度條Progress Bar程式線上教學
網址命名(REST Resource Naming)雖然沒有強制規定,但是因為已經行之有年,是有規則與默契的,下面我們就來看一下命名的建議:
網址中,那些部分要命名?
網址中,那個部份是需要命名的呢?以:'http://www.ietf.org/rfc/rfc2396.txt' 這個例子來說:
更簡單的說,我們只要管:'/rfc' 跟 '/rfc2396.txt' 這部分的「資源」名稱就好,「資源」= 「Resource」,這就是為什麼「網址命名」稱之為「REST Resource Naming」了,天啊,繞了好大一圈,希望你還沒有睡著。
以下為了方便,我將要命名的部分,一概稱之為「資源(Resource)」。
「資源」的建議命名方式:
讓我們先看一些實例:
原則上,所有的資源都應該使用名詞,我認為除了要與接下來要介紹的 http 動詞做區分外,還可以用單複數讓命名更精確,由上面的例子,我們發現一般資源命名大概都可以用複數,只有唯一的資源命名時(永遠就只有一份的資源,如:使用者購物紀錄,使用者設定),要用單數名詞。
以下是更進一步的建議:
其他命名注意事項:
還想要更了解?這兩篇值得一讀:
CRUD 呼叫 HTTP Verbs(動詞):
為什麼要用電腦呢?有一個很重要的理由,就是需要快速存取大量的資料,存取資料說到底,也不過就是以下這四的動作:
英文很常用首字母縮寫,所以這四個動作,就被稱為 CRUD,我是覺得用 CRUD 很迷濛,講「新增修改刪除」更直接,但是在工程師的世界裡,大家也用習慣了,就請接受吧。
網站開發當然也離不開存取資料,所以 CRUD 也自然就是網站開發的必須重點功能,那,CRUD 又如何轉化成網站存取資料的動作呢?讓我們看看:
2. POST /products
2. 按 submit 後,將 products 資料新增到資料庫。
2. PATCH /products/3
2. 按 submit 後,將 products (編號 3)資料庫的資料更新。
在上面的表中,我們看到了一些新東西:GET/POST/PATCH/DELETE,這些就是 HTTP verbs,也就是 「動詞」,正式的稱呼是 requst methods,我們來看看有那些。
Rails 在 4.0 後,就不用 PUT 而全面改用 PATCH 了,head 也不常用,所以,我們只要懂 GET/POST/PATCH/DELETE 這四個動作就好,棒!
很好,那你會問,我不是說網站就是使用者透過網址 「URL」來與網站伺服器「互動」,那,這些動詞要怎麼在瀏覽器上輸入?
A:答案是不行,你在瀏覽器上所輸入的網址都等於是執行 verb: GET(取得)的動作,其他的動詞,都是要透過網頁上的 Form 或是以連結的方式來呼叫,這也是為什麼你會看到上面介紹 CRUD 中的 create 新增,會對應到以下的這兩個動作:
Update 也一樣,都是需要在透過 Form 來產生相對應的 PATCH 動作;Delete 可以透過 Form 或 link,一般都是用 link 啦。
在 Rails 寫 routing
好了,你已經有足夠的網址規劃知識了,接下來,我們就來看看怎麼用 Rails 來寫出來。各位讀者如果要看完整的使用方法,還是要看 Rails 的官方文件啦,我這篇比較像是「劃重點」:
這是英文版的官方文件:Rails Routing from the Outside In — Ruby on Rails Guides,下面是中文版的:
Rails 路由:深入淺出 — Ruby on Rails 指南 — Ruby on Rails 指南:系統學習 Rails(Rails 4.2 版本)
Ruby on Rails 指南
在 Rails 中,網址的規劃是寫在 config/routes.rb 中,從檔名是 rb,一看就知道這就是一個 ruby 程式,我們先來看看 routes.rb 長什麼樣:
疑?很像 ruby 又不像,沒錯,因為 rails 已經完全的把它「包」在寫 routing 專用的
ActionDispatch::Routing
中了,所以這段碼其實是一個 DSL,domain specific language(領域特定語言),也就是在 rails 中專門寫 routing 用的,所以他是有專用的「指令」跟「語法」的,我們先來看看 routes.rb 的「語法」,以上面的「Rails routing example 1」的例子來說:這語法很簡單,那我們再來看看指令:
用 resource(s) 快速設定:
如果你的網站很簡單,也都是用 Rails scaffold 寫的,你可能根本不需要寫 routes.rb;如果你的網站有點複雜,沒辦法只用 Rails scaffold 寫,一般來說,也只要用 resource(s) 來做設定,一般也都夠用了。
resource 跟 resources 可以讓你快速的設定網址名稱與相對的動作,非常簡單快速,我們先來看 resources,只要短短的一行:
resources :books
(注意:是複數 resources 有's'),就等於已經寫了:哇,等於寫了七行!是的,簡單的一行 resources 指令,就等於完成七個網址命名,也設定了相對應的 controller 動作,仔細一看,這七個動作就包含了完整的 CRUD,還另外多了一個 index,那還需要另外加寫什麼?沒錯,一般來說這樣足夠了,除非你的網站很複雜。
如果你的資源只有一個,就是單數,例如:使用者設定,profile,也一樣只要用短短的一行:
resource :profile
(注意:是單數 resource,沒有's'),就可以幫你寫好所有 CRUD 的網址命名,不同於 resources 的是:就這樣,resource(s) 就講完了,你如果用 rails generate scaffold 自動產生程式碼,scaffold 也不過就是將 resource(s) 加入 routes.rb 中,原來如此,這樣也很夠用了。
為甚麼不管是單數還是複數的 resource(s) 都對應到複數的 controller 呢?
原來是會有需要同時建立出單數與複數的 resources,舉個例子,如果同時需要建立 resource :photo 與 resources :photos,這時,就都需要對應到 PhotosController 了。有此需求的人可以參考這篇,苦主寫得很清楚,或是用:resolve,這也可能是為什麼,官方文件在介紹單數 resource 時,後面是跟著一個 resolve 的。
resource(s) 產生的六、七個 action 動作還不夠用?沒問題,你可以用 collection 跟 member 來外加動作,如下(一樣就是上面那個「Rails routing example 1」的例子):
collection 及 member 有什麼不同?
巢狀 nesting resource(s)
當你有需要做偉大的巢狀網址規劃時⋯⋯ 什麼是巢狀?就是這樣啦:
/accounts/1/people/2/notes/3/comments/4
routes.rb 的寫法很簡單:
寫這段碼很簡單,只不過,四層,你是自找麻煩,一般的建議是,不要超過兩層。一般用 nesting 時,通常都不需要 [:show, :edit, :update, :destroy] ,所以可以用 Shallow 指令,設定為 true 就不會產生了。
如果有需要深入了解 nesting,除了官方文件外,如果看不太懂,這篇寫得很好:Buckblog: Nesting resources
如果到這裡的 resource(s) 都還不夠用,你可能命名的規劃有問題了,或,你的網站真的很複雜,這時,就需要繼續看下去了。
非 resource(s) 導向
什麼!resource(s) 還不夠用!太驚人了,你有可能是網址的命名規劃有問題,不過,也可能碰到疑難雜症,這時,就知能靠 rails 超級強大的非 resource(s) 導向功能了,哈哈哈哈,我在說廢話嗎?不是 resource(s) 當然就是「非」resource(s) 了,哎呦,二分法嘛,別鑽牛角尖,不過這個「非」resource(s) 導向,Rails 可是真的很強大的。
重點:非 resource(s) 導向最主要的功能,就是要解決,當奇奇怪怪的網址來敲門時,要如何分配給 server 端及做什麼樣的動作,這跟使用 resource(s) 的思考方式剛好相反。
params[:id] 可有可無
to: 'photos#show'
to: 'photos#show'
via: [:get, :post]
/photos
注意:Rails 的 get 是不會檢查 CSRF token,所以要
小心被 hacking,小心被連接到資料庫讀寫。
constraints: { id: /[A-Z]\d{5}/ }
constraints 是使用 regexp:如果來敲門的網址是
/photos/12345 就不會導向了,因為不符合
/[A-Z]\d{5}/ 的 regexp,
constraints: { id: /\d.+/ }
constraints 就是過濾 Request object,你可以過濾
其中的任何 Property 除了 format,甚至,你可以
建立自己的 restrict via matches?
to: 'photos#unknown'
這就是鬼牌,photos/12 或是 /photos/long/path/to/12
都會到 unknown action,params[:other] 會是:12 或
long/path/to/12。鬼牌通常放在最後,就是讓,所有
沒有 match 到的 photos/... 都導到這裡。
to: redirect('/articles/%{name}')
預設是 301,可以用 status: 302 改。
我們還可以設定固定的網址參數:例如,我們可已由 get 'photos/:id', to: 'photos#show', defaults: { format: 'jpg' } 來設定網址參數 params[:format] ,它會被固定設成 'jpg',而且不會被改變。
一些讓你更懶的設定
進一步客製化你的 Routing
Rails 的 routing 非常的強大,你幾乎可以任意的改變所有的預設,不過,我認為這些都很少會有機會用到,不需要學,只要大概知道有那些功能,到時再看就好,以下就是一些客製化選項:
還有一些更少用的,有需要再看吧。
多國語言網址命名
如果你的網站需要支援多國語言,你的網址當然也要能把使用者帶到對的語言,目前通用的多國語言的網址命名方式,大概有以下三種:
google 不建議 #2,也就是不建議將將「語言」設定在網址參數 params 中,不過也沒說不行,#1 將「語言」放在網域中,這要很會 SSL 設定,不是很好搞,我覺得 #3 將「語言」夾在網址中,應該是相對比較容易的選項。
詳細的設定我就不寫了,下面這篇文章是很好的參考,作者還長的很美呢。
Step-by-Step Guide to Providing Multi-Language Support for Your Rails App — Find out how to provide a multilanguage support for an app built with Ruby on Rails. Learn how to detect user's locale and translate dynamic and static content. old.yalantis.com
如果你的網站才剛要開發,下面這個 GEM 看起來很不錯用,我是沒用過啦。
enriclluelles/route_translator — Translate your rails app route to various languages without the hassle - enriclluelles/route_translator
GitHub
什麼是 (.format)?
在 Rails routing 的文件中,有很多(.format),乍看之下,真不知道是指什麼,有人說,這是指routing中的 .json、.xml 等副檔名網址,會轉成 params[:format] 參數,還有人說是 mine:type,到底是什麼呢?
A:答案是,mine:type,也就是 request.format,也就要透過下面這段碼來做判斷執行:
你可能不知道,Rails 有很多 mine:type,有興趣點下面這個連結,你就會發現,哇,這麼多啊。
rails/rails — Ruby on Rails. Contribute to rails/rails development by creating an account on GitHub.
GitHub
查看 & 測試
要查看 Rails 的 routing 輸出,有兩個常用的方法:
Rails 提供以下三個測試方法:
helper 裡的變數
routing 可以在 view 中被 link_to 產生,而 link_to 可以使用
url_for()
來轉換變數成 path,這很好用但是也很昏,所以我在結尾來說一下,例如:url_for()
會自己找到對應的 routing,這也就是為什麼上面的 path 可以被url_for()
取代,更進一步,連url_for()
都不用寫了。上面的例子可能不常見,但是下面的寫法就太普遍了,
Routing 就這樣
如果你能看到這裡,你應該已經對 Rails 的 routing 有夠深度的了解了,也對網址規劃有一定的能力了,恭喜你!
Rails 本質上,就是一個架在 Rack 上的應用程式,如果你還想要更進一步無拘無束的使用 routing,那就要研究 Rack 了,常用的 web server Puma 也是 Rack 的應用,除了 Rails 外,SINATRA 等 framework 也使用 Rack,當然,這個題目太大了,我也不會,有興趣就祝你好運了!
rack/rack — A modular Ruby web server interface. Contribute to rack/rack development by creating an account on GitHub.
GitHub