巨觀學習 Rails 的 routing 路由

紅寶鐵軌客
Join to follow...
Follow/Unfollow Writer: 紅寶鐵軌客
By following, you’ll receive notifications when this author publishes new articles.
Don't wait! Sign up to follow this writer.
WriterShelf is a privacy-oriented writing platform. Unleash the power of your voice. It's free!
Sign up. Join WriterShelf now! Already a member. Login to WriterShelf.
寫程式中、折磨中、享受中 ......
820   0  
·
2020/09/29
·
19 mins read


開發網站一個網站或是服務,簡單說,就是使用者透過網址 「URL」來與網站的伺服器「互動」,所以,網址的規劃應該就是網站開發的起點了!

網址的規劃是有規矩與習慣的

互動就好像是對話,使用者(Client 端)發出一個「URL」,網站伺服器(Server 端)做出「內容」,並回應一個新的「URL」,就這樣一來一往,例如:

  • 使用者(Client 端):「我要看你們的產品」,
    • 目前,大家普遍的 URL 用法是:「www.xx.com/products」,
    • 可是,請想想,為什麼要用 /products 不用 /product-list 呢?
  • 使用者(Client 端):「我要改變產品 A19 的定價」,使用者點了 A19 旁邊的「編輯」按鈕,
    • 這個編輯網址,大家普遍的用法是:「www.xx.com/products/A19/edit」,使用者改好後,按 submit 更新,通常會又回到 www.xx.com/products
    • 請再想想,為什麼不是 /edit/A19/products 呢?

事實上,網址的規劃是可以隨你喜愛,甚至亂搞的,只是,大家使用全球資訊網(World Wide Web)已經超過 30 年了,慢慢的,大家也建立了一些習慣用法,這些用法沒有絕對的好或是不好,但是,習慣總是經驗的累積,如果自己再創一套新的,不只使用者看起來怪,可能還會碰到新的問題,將來他人來維護你開發的網站也會很辛苦的,所以, 當我們在開發網站時,我們應該要先認識這些習慣用法。

 model 名稱 = 網址命名

在 Rails 中,網址的規劃,就是 routing,是寫在 config/routes.rb 中,詳細的使用說明,各位還是請看這:官方文件,這篇文章沒有任何意圖想要取代官方文件,但是,我希望能以更高的角度來看網址規劃,先讀完這篇,應該會更容易了解官方文件中想要表達的是什麼,Rails 的官方文件很有趣,很多細節是寫給熟手看的。

網址要規劃?很多 Rails 的開發者可能根本都沒想過,因為 Rails 太好用了,我想很多人會選擇用 rails 來開發網站,應該都是被美麗又厲害的 rails generate「scaffold」給吸引來的,多厲害啊,就像下面這樣,一行指令,什麼都寫好了,真棒!(如果你還沒用過 Rails,rails generate scaffold 指令就只是一個 Rails 自動程式產生器而已。)

$ rails generate scaffold product name 'price:decimal{7,2}'
      invoke
     ....
       route    resources :products
    ....

如上,「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' 這個例子來說:

  • 'http:' 是服務協議,命名不用管;
  • 'www.ietf.org' 是網域名稱,我們也不用管;
  • '/rfc' 跟 '/rfc2396.txt' 才是我們要命名的地方。

 更簡單的說,我們只要管:'/rfc' 跟 '/rfc2396.txt' 這部分的「資源」名稱就好,「資源」= 「Resource」,這就是為什麼「網址命名」稱之為「REST Resource Naming」了,天啊,繞了好大一圈,希望你還沒有睡著。

以下為了方便,我將要命名的部分,一概稱之為「資源(Resource)」。

「資源」的建議命名方式:

讓我們先看一些實例:

  • GitHub 中的訂閱:/user/subscriptions,user 是單數,因為是指目前登入的使用者,subscriptions 用複數名詞,這是指使用者的訂閱列表,如果是訂閱,會用動詞 subscribe
  • Amazon 未登入時的 Kindle 書選購列表:/Kindle-eBooks,列表就是很多書,所以用複數。
  • Amazon 登入的使用者的閱覽紀錄:/gp/history,因為登入的使用者就只有一個,所以用的是單數,有趣的是,使用者不是叫 user 而是叫 gp;閱覽紀錄是 histroy,名詞,單複數的一樣。
  • YouTube 登入的使用者上傳的影片列表:/feed/my_videos,上傳影片的列表,當然是複數。
  • barnes&noble 未登入時的童書列表:b/books/kids,這資源有層級架構,小孩書是書的一種,列表,當然是複數。

原則上,所有的資源都應該使用名詞,我認為除了要與接下來要介紹的 http 動詞做區分外,還可以用單複數讓命名更精確,由上面的例子,我們發現一般資源命名大概都可以用複數,只有唯一的資源命名時(永遠就只有一份的資源,如:使用者購物紀錄,使用者設定),要用單數名詞。

以下是更進一步的建議:

  • 單一文件(doucment):使用單數,因為只有單一個資源,例如:/user/profile
  • 集合(collection):使用複數,是指一群資料,例如:/users
  • 貯藏庫(store):使用複數,一般是指使用者的自訂資料,例如:/song-management/users/{id}/playlists
  • 控制(controller):這裡要用動詞,是指使用者的動作,例如:/song-management/users/{id}/playlist/play

其他命名注意事項:

  • 兩個字之間用減號-或底線_隔開皆可,但是建議使用減號-,不管用那一個,要保持一致。
  • 全部應該都使用小寫。
  • 不要含檔案型態,如:.xml,.html... 等
  • 絕對不需要將 CRUD 放入,也就是 create/read/update/delete,應為這是由 http verb 負責,不知道什麼是 CRUD 跟 http verb? 沒關係,接下來馬上介紹。

還想要更了解?這兩篇值得一讀:

  1. REST Resource Naming Guide - REST API Tutorial
  2. 簡明RESTful API設計要點
CRUD 呼叫 HTTP Verbs(動詞):

為什麼要用電腦呢?有一個很重要的理由,就是需要快速存取大量的資料,存取資料說到底,也不過就是以下這四的動作:

  1. Create: 建立新增資料。
  2. Read: 讀取資料。
  3. Update: 修改資料。
  4. Delete: 刪除資料。

英文很常用首字母縮寫,所以這四個動作,就被稱為 CRUD,我是覺得用 CRUD 很迷濛,講「新增修改刪除」更直接,但是在工程師的世界裡,大家也用習慣了,就請接受吧。

網站開發當然也離不開存取資料,所以 CRUD 也自然就是網站開發的必須重點功能,那,CRUD 又如何轉化成網站存取資料的動作呢?讓我們看看:

Create 1. GET /products/new
2. POST /products
1. 顯示新增 products 資料的表格,
2. 按 submit 後,將 products 資料新增到資料庫。
Read GET /products/3 顯示產品資料(編號是 3 )
Update 1. GET /products/3/edit
2. PATCH /products/3
1. 顯示修改 products(編號 3)資料的表格,
2. 按 submit 後,將 products (編號 3)資料庫的資料更新。
Delete DELETE /products/3 刪除 products (編號 3)資料

在上面的表中,我們看到了一些新東西:GET/POST/PATCH/DELETE,這些就是 HTTP verbs,也就是 「動詞」,正式的稱呼是 requst methods,我們來看看有那些。



Safe? Idempotent?
GET 讀取資料 Y Y
POST 新增一筆資料 N N
PATCH 更新(部分)資料 N N
PUT 更新或新增資料 N Y
DELETE 刪除資料 N Y
HEAD
只讀取 HTTP header Y Y
其他(wiki

Rails 在 4.0 後,就不用 PUT 而全面改用 PATCH 了,head 也不常用,所以,我們只要懂 GET/POST/PATCH/DELETE 這四個動作就好,棒!

很好,那你會問,我不是說網站就是使用者透過網址 「URL」來與網站伺服器「互動」,那,這些動詞要怎麼在瀏覽器上輸入?

A:答案是不行,你在瀏覽器上所輸入的網址都等於是執行 verb: GET(取得)的動作,其他的動詞,都是要透過網頁上的 Form 或是以連結的方式來呼叫,這也是為什麼你會看到上面介紹 CRUD 中的 create 新增,會對應到以下的這兩個動作:

  1. GET /products/new - 這是新增 products 資料的第一步,這個網址會呼叫伺服器上會執行 GET 動作,然後網頁上應該會顯示出一個 products 的資料表格(Form),等待使用者輸入資料,使用者在按了 Form 中的 submit 後, 就會呼叫下面的 POST 動作,這個 POST 的動作內容是寫在 Form 裡,所以 POST 可以被呼叫了。
  2. POST /products - 伺服器將過送來的 products 資料新增到資料庫,這段程式在伺服器中執行時,使用者是看不到的。一般來說,執行完後這段後,會將網頁跳到顯示執行完畢的狀況。

Update 也一樣,都是需要在透過 Form 來產生相對應的 PATCH 動作;Delete 可以透過 Form 或 link,一般都是用 link 啦。

在了解 HTTP verbs 時,還有兩個常聽到的名詞:

  • Idempotent:是指重複執行會不會有相同的結果,POST,PUT 和 PATCH 不是 idempotent,也就是說,這幾個操作是不可以 Retry (重試)的,例如:POST 兩次相同資料,就會產生兩筆資料,自然不是 idempotent;如果是 idempotent,不管執行結果幾次都會是一樣的。
  • Safe:是指這操作會不會改變伺服器端的資源狀態,只有 GET 和 HEAD 是 Safe 操作,Safe 特性會影響是否可以被快取(cache),重要嗎?瀏覽器倒退鍵會到那頁,網站速度都跟這有關。
在 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 長什麼樣:

# Rails routing example 1:

Rails.application.routes.draw do
  # 用 resource(s) 快速設定
  resources :order, :only => [ :show, :update, :destroy ] do
    collection do
      patch 'cancelled' # orders/cancelled(.:format)
    end
    member do
      delete :cancel # orders/:id/cancel(.:format)
    end
  end

  # 一個一個設定
  get 'photos(/:id)', to: :display 
    # 到 PhotosController 的 display action,不管有沒有 params[:id] 
end

疑?很像 ruby 又不像,沒錯,因為 rails 已經完全的把它「包」在寫 routing 專用的 ActionDispatch::Routing 中了,所以這段碼其實是一個 DSL,domain specific language(領域特定語言),也就是在 rails 中專門寫 routing 用的,所以他是有專用的「指令」跟「語法」的,我們先來看看 routes.rb 的「語法」,以上面的「Rails routing example 1」的例子來說: 

  • 其中 patch 'cancelled' 的 「cancelled」就是我們要命名的「資源」名稱, 
  • get 'photos(/:id)':中的 「:id」指的是網址參數 params[:id],在括弧 ( ) 中就是指這個網址參數可有可無,
  •  [ :show, :update, :destroy ] 中的 「:show」指的是相對應的 controller 中的 action 「show」。

這語法很簡單,那我們再來看看指令:

用 resource(s) 快速設定:

如果你的網站很簡單,也都是用 Rails scaffold 寫的,你可能根本不需要寫 routes.rb;如果你的網站有點複雜,沒辦法只用 Rails scaffold 寫,一般來說,也只要用 resource(s) 來做設定,一般也都夠用了。

resource 跟 resources 可以讓你快速的設定網址名稱與相對的動作,非常簡單快速,我們先來看 resources,只要短短的一行:resources :books(注意:是複數 resources 有's'),就等於已經寫了:


HTTP Verb 網址名稱 Controller 中的 Action 說明
1 GET /books books#index 列出所有的 books 
2 GET /books/new books#new 顯示新增 books 的資料表格
3 POST /books books#create 新增 books 資料
4 GET /books/:id books#show 列出指定的那筆 book 資料
5 GET /books/:id/edit books#edit 顯示待修改的指定 book 資料表格
6 PATCH /photos/:id photos#update 更新指定的 book 資料
7 DELETE /photos/:id photos#destroy 刪除指定的那筆 book 資料

哇,等於寫了七行!是的,簡單的一行 resources 指令,就等於完成七個網址命名,也設定了相對應的 controller 動作,仔細一看,這七個動作就包含了完整的 CRUD,還另外多了一個 index,那還需要另外加寫什麼?沒錯,一般來說這樣足夠了,除非你的網站很複雜。

如果你的資源只有一個,就是單數,例如:使用者設定,profile,也一樣只要用短短的一行:resource :profile(注意:是單數 resource,沒有's'),就可以幫你寫好所有 CRUD 的網址命名,不同於 resources 的是:

  1. 單數的 resource :profile 還是對應到複數的 controller profiles,也就是說,不管單複數的 resource(s) 都是對應到複數的 controller,這點很重要!
  2. 單數 resource 沒有產生第一個 index,有點廢話,就只有一個資源,當然不需要能列出所有的 profile 的功能。
  3. 單數 resource 也沒有 /:id,也就是沒有網址參數 params[:id],好像還是廢話,資源就只有一個,當然不需要指定那一筆了。

就這樣,resource(s) 就講完了,你如果用 rails generate scaffold 自動產生程式碼,scaffold 也不過就是將 resource(s) 加入 routes.rb 中,原來如此,這樣也很夠用了。

為甚麼不管是單數還是複數的 resource(s) 都對應到複數的 controller 呢?

原來是會有需要同時建立出單數與複數的 resources,舉個例子,如果同時需要建立 resource :photo 與 resources :photos,這時,就都需要對應到 PhotosController 了。有此需求的人可以參考這篇,苦主寫得很清楚,或是用:resolve,這也可能是為什麼,官方文件在介紹單數 resource 時,後面是跟著一個 resolve 的。

resource :geocoder
resolve('Geocoder') { [:geocoder] }

resource(s) 產生的六、七個 action 動作還不夠用?沒問題,你可以用 collection 跟 member 來外加動作,如下(一樣就是上面那個「Rails routing example 1」的例子):

resources :order, :only => [ :show, :update, :destroy ] do
  collection do
    patch 'cancelled' # orders/cancelled(.:format)
  end
  member do
    delete :cancel # orders/:id/cancel(.:format)
  end
end

collection 及 member 有什麼不同?

  • member:會產生 :id,也就是用在需要指定資料時,例如:預覽某一張照片 /photos/1/preview。
  • collection:不會產生 :id,用在那?例如:search。
巢狀 nesting resource(s)

當你有需要做偉大的巢狀網址規劃時⋯⋯ 什麼是巢狀?就是這樣啦:

/accounts/1/people/2/notes/3/comments/4

 routes.rb 的寫法很簡單:

accounts.resources :people do |people|
  people.resources :notes do |notes|
    notes.resources :comments, shallow: true
  end
end

寫這段碼很簡單,只不過,四層,你是自找麻煩,一般的建議是,不要超過兩層。一般用 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) 的思考方式剛好相反。


非 resource(s) 導向 來敲門的網址 把它帶到
網址導向 get 'photos(/:id)', to: :display /photos/3 { controller: 'photos', action: 'display', id: '3' }
params[:id] 可有可無
網址導向 get 'photos/:id/:user_id',
to: 'photos#show'
/photos/3/5 { controller: 'photos', action: 'show', id: '1', user_id: '2' }
網址導向 get 'photos/:id/with_user/:user_id',
to: 'photos#show'
/photos/1/with_user/2 /photos/1/with_user/2{ controller: 'photos', action: 'show', id: '1', user_id: '2' }
verb導向 match 'photos', to: 'photos#show',
via: [:get, :post]
get 或 post 時的
/photos
{ controller: 'photos', action: 'show' },
注意:Rails 的 get 是不會檢查 CSRF token,所以要
小心被 hacking,小心被連接到資料庫讀寫。
內容導向 get 'photos/:id', to: 'photos#show',
constraints: { id: /[A-Z]\d{5}/ }
/photos/A12345 { controller: 'photos', action: 'show', id: 'A12345'}
constraints 是使用 regexp:如果來敲門的網址是
/photos/12345 就不會導向了,因為不符合
/[A-Z]\d{5}/ 的 regexp,
內容導向 get '/:id', to: 'articles#show',
constraints: { id: /\d.+/ }
/1-hello-world { controller: 'photos', action: 'show', id: '1-hello-world'}
constraints 就是過濾 Request object,你可以過濾
其中的任何 Property 除了 format,甚至,你可以
建立自己的 restrict via matches?
鬼牌導向
get 'photos/*other',
to: 'photos#unknown'
/photos/badguy { controller: 'photos', action: 'unknown', other: 'badguy'}
這就是鬼牌,photos/12 或是 /photos/long/path/to/12
都會到 unknown action,params[:other] 會是:12 或
long/path/to/12。鬼牌通常放在最後,就是讓,所有
沒有 match 到的 photos/... 都導到這裡。
轉址
get '/stories/:name',
to: redirect('/articles/%{name}')
/stories/3pigs /articles/3pigs
預設是 301,可以用 status: 302 改。
root root to: 'pages#main' / { controller: 'pages', action: 'main'}

我們還可以設定固定的網址參數:例如,我們可已由 get 'photos/:id', to: 'photos#show', defaults: { format: 'jpg' } 來設定網址參數 params[:format] ,它會被固定設成 'jpg',而且不會被改變。

一些讓你更懶的設定
  • concerns: 會重複用到的設定,可以寫在 concerns 裡,只是也省不了幾個字就是了。
  • namespace:  可以在前面加一個資源名稱,例如:
namespace :admin do
  resources :articles
end

index = /admin/articles
show = /admin/articles/:id
...
進一步客製化你的 Routing

Rails 的 routing 非常的強大,你幾乎可以任意的改變所有的預設,不過,我認為這些都很少會有機會用到,不需要學,只要大概知道有那些功能,到時再看就好,以下就是一些客製化選項:

  • 改到另一個 controller,
  • 改變呼叫的 action,
  • 設定 constraints,只讓特定的 path 或網址參數做動作,
  • 改變 path helper 的名稱,
  • path 加上 prefix,
  • 只產生部分 HTTP verb 動詞,也就是動作,
  • 多國語言:path 跟網址參數都可以換成不是英文的其他國語言,

還有一些更少用的,有需要再看吧

多國語言網址命名

如果你的網站需要支援多國語言,你的網址當然也要能把使用者帶到對的語言,目前通用的多國語言的網址命名方式,大概有以下三種:

  1. 將「語言」放在網域中,如:https://application.de,或是 https://german-domain.net
  2. 將「語言」設定在網址參數中,如:https://example.com/cats?locale=de
  3. 將「語言」夾在網址中,如:https://www.application.com/de/cats

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,也就要透過下面這段碼來做判斷執行:

respond_to do |format|
  format.html { ... }
  format.pdf { ... }
  format.json { ... }
end

你可能不知道,Rails 有很多 mine:type,有興趣點下面這個連結,你就會發現,哇,這麼多啊。

rails/rails — Ruby on Rails. Contribute to rails/rails development by creating an account on GitHub.
GitHub

查看 & 測試

要查看 Rails 的 routing 輸出,有兩個常用的方法:

  • 用瀏覽器看
    • 輸入 http://localhost:3000/rails/info/routes,這很好用,可以互動,可以搜尋特定的名稱。
  • 在終端機上看:
    • 打 rails routes 就可以看到完整的列表,太長?可以用下面的方式搜尋名稱:
      • $ bin/rails routes -g POST
      • $ bin/rails routes -g admin
    • 也可以搜尋 controller 
      • $ bin/rails routes -c users
      • $ bin/rails routes -c admin/users

Rails 提供以下三個測試方法:

  • assert_generates - 產生出來的路由是否正確。
  • assert_recognizes - 給的路徑是否能識別。
  • assert_routing - 雙向檢查,也就是上面的綜合體。
helper 裡的變數

routing 可以在 view 中被 link_to 產生,而 link_to 可以使用 url_for() 來轉換變數成 path,這很好用但是也很昏,所以我在結尾來說一下,例如:

  • link_to 'Ad details', magazine_ad_path(@magazine, @ad)
    • 可以寫成 ⇒ link_to 'Ad details', url_for([@magazine, @ad])
      • 可以再簡寫成 ⇒ link_to 'Ad details', [@magazine, @ad]

url_for() 會自己找到對應的 routing,這也就是為什麼上面的 path 可以被 url_for() 取代,更進一步,連 url_for() 都不用寫了。

上面的例子可能不常見,但是下面的寫法就太普遍了,

  • link_to 'Magazine details', @magazine
    • 其實就是 ⇒ link_to 'Magazine details', url_for(@magazine)
      • 如果 @magazine 的值是 5,那就等於是 ⇒ /magazines/5 了。
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




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.




Share this article:
About the Author

很久以前就是個「寫程式的」,其實,什麼程式都不熟⋯⋯
就,這會一點點,那會一點點⋯⋯




Join the discussion now!
Don't wait! Sign up to join the discussion.
WriterShelf is a privacy-oriented writing platform. Unleash the power of your voice. It's free!
Sign up. Join WriterShelf now! Already a member. Login to WriterShelf.