1 Rails 路由器的目的
Rails 路由器(router)識別網址,分配給對應的 Controller 動作處理。Rails 路由器同時也可用來產生路徑與網址,避免在 View 裡面用字串來把路徑與網址寫死。
1.1 路由器如何分派請求
當 Rails 收到如下請求時:
GET /patients/17
會詢問路由器,匹配的 Controller 動作是那個。若第一筆匹配的路由為:
get '/patients/:id', to: 'patients#show'
則請求會分派給 PatientsController
的 show
動作處理,且 params
裡有 { id: '17' }
參數。
1.2 產生路徑與網址
Rails 路由器也可以產生路徑與網址。若上例的路由改寫為:
get '/patients/:id', to: 'patients#show', as: 'patient'
且應用程式 Controller 裡有以下程式碼:
@patient = Patient.find(17)
並有對應的 View:
<%= link_to 'Patient Record', patient_path(@patient) %>
則路由器便會給 patient_path(@patient)
產生路徑 /patients/17
。這使得程式碼更容易了解。注意,使用路由輔助方法(patient_path
)時,無需指定 ID。
2 資源式路由:Rails 的預設路由
資源式路由可替資源式 Controller 快速宣告出所有常見的路由。與其挨個替每個動作( index
、show
、new
、edit
、create
、update
以及 destroy
)宣告路由,資源式路由宣告一行解決。
2.1 Web 世界裡的資源
瀏覽器向 Rails 請求頁面時,透過使用具體的 HTTP 動詞,如 GET
、POST
、PATCH
、PUT
以及 DELETE
,往 URL 發出請求。每個動詞都是對資源的一種操作。資源式路由將請求對應到 Controller 的動作。
當 Rails 應用程式收到下面這個請求時:
DELETE /photos/17
會詢問路由器,路由器會決定該交給那個 Controller 的那個動作處理。若第一筆匹配的路由為:
resources :photos
Rails 會將請求分派給 PhotosController
的 destroy
方法,且 params
裡有 { id: '17' }
參數。
2.2 CRUD、HTTP 動詞以及動作
在 Rails 裡,資源式路由提供 HTTP 動詞、URL、Controller 動作,這三者的對應關係。按照慣例,每個動作會對應到資料庫特定的 CRUD 操作。假設路由檔案裡有一條路由宣告為:
resources :photos
會建立出七筆不同的路由,皆對應到 PhotosController
:
HTTP 動詞 | 路徑 | Controller#動作 | 用途 |
---|---|---|---|
GET | /photos | photos#index | 顯示所有圖片 |
GET | /photos/new | photos#new | 回傳建立新圖片的表單 |
POST | /photos | photos#create | 建立新圖片 |
GET | /photos/:id | photos#show | 顯示特定圖片 |
GET | /photos/:id/edit | photos#edit | 回傳編輯圖片的表單 |
PATCH/PUT | /photos/:id | photos#update | 更新特定圖片 |
DELETE | /photos/:id | photos#destroy | 刪除特定圖片 |
因為路由器使用 HTTP 動詞與 URL 來匹配進來的請求,所以可將四個 URL 對應到七種不同的動作。
路由依據宣告的順序來匹配。若在 get 'photos/poll'
之前宣告了 resources :photos
,則 show
動作會先匹配到 resources :photos
。若想將 get 'photos/poll'
的匹配順序提前,提到 resources :photos
之前即可。
2.3 路徑與 URL 的輔助方法
新建一筆資源式的路由,同時會給應用程式裡的 Controller 加入一些輔助方法。以 resources :photos
為例,可用的輔助方法有:
輔助方法 | 用途 |
---|---|
photos_path |
回傳 /photos
|
new_photo_path |
回傳 /photos/new
|
edit_photo_path(:id) |
回傳 /photos/:id/edit (例如 edit_photo_path(10) 會回傳 /photos/10/edit ) |
photo_path(:id) |
回傳 /photos/:id (例如 photo_path(10) 回傳 /photos/10 ) |
這些輔助方法有對應的 *_url
形式(像是 photos_url
),*_url
會回傳完整的路徑,包含了主機、埠口以及路徑。
2.4 同時定義多筆資源
如需同時給多個資源建立路由,可以用一行 resources
宣告完成,可節省些打字的時間:
resources :photos, :books, :videos
等同於:
resources :photos resources :books resources :videos
2.5 單數資源
有些資源無需 ID 便能查詢。舉個例子,希望 /profile
顯示目前登入使用者的個人檔案。可以用單數資源(Singular resource),把 /profile
(而不是 /profile/:id
)對應到 show
動作:
get 'profile', to: 'users#show'
:to
選項傳入字串要使用 controller#action
的形式,傳入符號會直接對應到動作名稱。
get 'profile', to: :show
以下這筆單數資源式路由:
resource :geocoder
會建立出六筆不同的路由,皆對應到 GeocodersController
:
HTTP 動詞 | 路徑 | Controller#動作 | 用途 |
---|---|---|---|
GET | /geocoder/new | geocoders#new | 回傳建立 geocoder 的表單 |
POST | /geocoder | geocoders#create | 建立新 geocoder
|
GET | /geocoder | geocoders#show | 顯示唯一的 geocoder 資源 |
GET | /geocoder/edit | geocoders#edit | 回傳編輯 geocoder 的表單 |
PATCH/PUT | /geocoder | geocoders#update | 更新唯一的 geocoder 資源 |
DELETE | /geocoder | geocoders#destroy | 刪除 geocoder 資源 |
有時單數(/account
)與複數路由(/accounts/45
)想交給同樣的 Controller 處理,或是把單數資源對應到複數 Controller 上。舉個例子,resource :photo
與 resources :photos
同時建立出單數與複數的路由,皆對應到 PhotosController
。
單數的資源式路由會產生以下輔助方法:
輔助方法 | 用途 |
---|---|
new_geocoder_path |
回傳 /geocoder/new
|
edit_geocoder_path |
回傳 /geocoder/edit
|
geocoder_path |
回傳 /geocoder
|
和複數資源的路由相同,皆有對應的 *_url
形式,會回傳完整的路徑,包含了主機、埠口以及路徑。
有一個存在已久的 Bug 導致 form_for
無法自動處理好單數資源。解決辦法是給表單明確指定 URL:
form_for @geocoder, url: geocoder_path do |f|
2.6 Controller 命名空間與路由
有時可能想把一堆 Controllers 放在同個命名空間下管理。最常見的場景是需要把管理用的 Controller 放在 Admin::
命名空間下。首先將這些 Controllers 搬到 app/controllers/admin
資料夾底下,接著在路由裡宣告:
namespace :admin do resources :articles, :comments end
會給 Articles
與 Comments
Controllers 在 Admin::
命名空間下建出路由。比如 Admin::ArticlesController
,Rails 會產生以下路由:
HTTP 動詞 | 路徑 | Controller#動作 | 輔助方法 |
---|---|---|---|
GET | /admin/articles | admin/articles#index | admin_articles_path |
GET | /admin/articles/new | admin/articles#new | new_admin_article_path |
POST | /admin/articles | admin/articles#create | admin_articles_path |
GET | /admin/articles/:id | admin/articles#show | admin_article_path(:id) |
GET | /admin/articles/:id/edit | admin/articles#edit | edit_admin_article_path(:id) |
PATCH/PUT | /admin/articles/:id | admin/articles#update | admin_article_path(:id) |
DELETE | /admin/articles/:id | admin/articles#destroy | admin_article_path(:id) |
若想把路徑拿掉 /admin
前綴,則可以這麼宣告:
scope module: 'admin' do resources :articles, :comments end
如只有一筆資源,則可簡寫為:
resources :articles, module: 'admin'
若路由希望是 /admin/articles
,但想拿掉 Admin::
的前綴,可以這麼宣告:
scope '/admin' do resources :articles, :comments end
如只有一筆資源,則可簡寫為:
resources :articles, path: '/admin/articles'
以上這些例子,若沒有使用 scope
,則輔助方法保持不變。看看上面最後一個使用 scope
的例子(與前個表格對比看看那裡不一樣),Rails 會產生以下路由:
HTTP 動詞 | 路徑 | Controller#動作 | 輔助方法 |
---|---|---|---|
GET | /admin/articles | articles#index | articles_path |
GET | /admin/articles/new | articles#new | new_article_path |
POST | /admin/articles | articles#create | articles_path |
GET | /admin/articles/:id | articles#show | article_path(:id) |
GET | /admin/articles/:id/edit | articles#edit | edit_article_path(:id) |
PATCH/PUT | /admin/articles/:id | articles#update | article_path(:id) |
DELETE | /admin/articles/:id | articles#destroy | article_path(:id) |
若需要在 namespace
區塊裡,使用不同的命名空間。可以指定 Controller 的絕對路徑:get '/foo' => '/foo#index'
。
2.7 嵌套資源
資源是其它資源的子資源是很常見的情況。舉例來說,假設應用程式有雜誌與廣告兩個 Model:
class Magazine < ActiveRecord::Base has_many :ads end class Ad < ActiveRecord::Base belongs_to :magazine end
這種關係可以用嵌套路由來描述。在這個情況裡,可以這麼宣告路由:
resources :magazines do resources :ads end
上面會建立 MagazinesController
的路由,也會給 AdsController
建立路由。Ad
的路徑裡會需要引用 Magazine
資源:
HTTP 動詞 | 路徑 | Controller#動作 | 用途 |
---|---|---|---|
GET | /magazines/:magazine_id/ads | ads#index | 顯示特定雜誌的所有廣告 |
GET | /magazines/:magazine_id/ads/new | ads#new | 回傳給特定雜誌新建廣告的表單 |
POST | /magazines/:magazine_id/ads | ads#create | 建立屬於特定雜誌的廣告 |
GET | /magazines/:magazine_id/ads/:id | ads#show | 顯示屬於特定雜誌的廣告 |
GET | /magazines/:magazine_id/ads/:id/edit | ads#edit | 回傳編輯屬於特定雜誌廣告的表單 |
PATCH/PUT | /magazines/:magazine_id/ads/:id | ads#update | 更新屬於特定雜誌的廣告 |
DELETE | /magazines/:magazine_id/ads/:id | ads#destroy | 刪除屬於特定雜誌的廣告 |
同時這也會建立像是 magazine_ads_url
以及 edit_magazine_ad_path
的路由輔助方法。這些方法可接受 Magazine
的實體作為第一個參數:magazine_ads_url(@magazine)
。
2.7.1 嵌套的限制
嵌套資源也可以放在其它的嵌套資源裡,譬如:
resources :publishers do resources :magazines do resources :photos end end
多層嵌套很快的變得很難處理。在這個情況裡,應用程式需要識別像是下面的路由:
/publishers/1/magazines/2/photos/3
對應的路由輔助方法則變成: publisher_magazine_photo_url
,需要指定三層的物件。這個情況已經足夠令人困惑,使得 Jamis Buck 寫出一篇流行的文章,文章總結了好的 Rails 的設計經驗準則:
嵌套資源永遠不要超過 1 層。
2.7.2 淺層嵌套
避免多層嵌套的方法之一,是將 Controller 的集合動作放在父資源的作用域底下,這樣可以有階層的概念,但不需要嵌套的成員動作。也就是說,只用最少的資源資訊來表示路由,像是:
resources :articles do resources :comments, only: [:index, :new, :create] end resources :comments, only: [:show, :edit, :update, :destroy]
這種做法在有意義的描述路由與深層嵌套之間取得平衡。上例還可以使用 :shallow
選項來簡寫:
resources :articles do resources :comments, shallow: true end
這種寫法產生的路由與上例相同。也可以對父資源指定 :shallow
選項,則父資源底下的資源都會是淺層嵌套:
resources :articles, shallow: true do resources :comments resources :quotes resources :drafts end
另有一個 shallow
方法,建立一個作用域區塊,其中的路由皆是淺層嵌套。以下範例會產生與前例相同的路由:
shallow do resources :articles do resources :comments resources :quotes resources :drafts end end
scope
方法有兩個選項,可以用來客製化淺層路由。:shallow_path
可以在 member path 前加上指定的前綴:
scope shallow_path: "sekret" do resources :articles do resources :comments, shallow: true end end
comments 資源會有下列路由:
HTTP Verb | Path | Controller#Action | Named Helper |
---|---|---|---|
GET | /articles/:article_id/comments(.:format) | comments#index | article_comments_path |
POST | /articles/:article_id/comments(.:format) | comments#create | article_comments_path |
GET | /articles/:article_id/comments/new(.:format) | comments#new | new_article_comment_path |
GET | /sekret/comments/:id/edit(.:format) | comments#edit | edit_comment_path |
GET | /sekret/comments/:id(.:format) | comments#show | comment_path |
PATCH/PUT | /sekret/comments/:id(.:format) | comments#update | comment_path |
DELETE | /sekret/comments/:id(.:format) | comments#destroy | comment_path |
:shallow_prefix
選項則是給 named helpers 加上前綴:
scope shallow_prefix: "sekret" do resources :articles do resources :comments, shallow: true end end
comments 資源會有下列路由:
HTTP Verb | Path | Controller#Action | Named Helper |
---|---|---|---|
GET | /articles/:article_id/comments(.:format) | comments#index | article_comments_path |
POST | /articles/:article_id/comments(.:format) | comments#create | article_comments_path |
GET | /articles/:article_id/comments/new(.:format) | comments#new | new_article_comment_path |
GET | /comments/:id/edit(.:format) | comments#edit | edit_sekret_comment_path |
GET | /comments/:id(.:format) | comments#show | sekret_comment_path |
PATCH/PUT | /comments/:id(.:format) | comments#update | sekret_comment_path |
DELETE | /comments/:id(.:format) | comments#destroy | sekret_comment_path |
2.8 Routing Concerns
Routing Concerns 允許將常見的路由宣告為可重用的,可在其他資源與路由裡使用。定義一個 Routing Concerns:
concern :commentable do resources :comments end concern :image_attachable do resources :images, only: :index end
這些 Concerns 可以在資源裡使用,來避免寫重複的程式碼,以及讓路由之間可以共享行為:
resources :messages, concerns: :commentable resources :articles, concerns: [:commentable, :image_attachable]
上例等價於:
resources :messages do resources :comments end resources :articles do resources :comments resources :images, only: :index end
Concerns 可以在任何地方使用,譬如在作用域,或是命名空間呼叫裡使用:
namespace :articles do concerns :commentable end
2.9 從物件建立路徑與 URL
除了使用路由輔助方法之外,Rails 也可以從一組參數,建出路徑與 URL。舉例來說,假設有以下路由:
resources :magazines do resources :ads end
在使用 magazine_ad_path
時,可以傳入 Magazine
與 Ad
的實體,而不需要傳入 ID:
<%= link_to 'Ad details', magazine_ad_path(@magazine, @ad) %>
也可以使用 url_for
,搭配一組物件,則 Rails 會自動決定要用那個路由:
<%= link_to 'Ad details', url_for([@magazine, @ad]) %>
這個情況裡,Rails 看到 @magazine
與 @ad
,會使用 magazine_ad_path
輔助方法。在像是 link_to
的輔助方法,可以直接指定物件,省略 url_for
:
<%= link_to 'Ad details', [@magazine, @ad] %>
若想要只連到雜誌:
<%= link_to 'Magazine details', @magazine %>
要連到不同的動作,在參數陣列的第一個元素,指定動作名稱即可:
erb
<%= link_to 'Edit Ad', [:edit, @magazine, @ad] %>
這種用法可以將 Model 的實體當做 URL 看待,是使用資源式路由的主要優勢之一。
2.10 新增更多資源式路由
不受限於七個預設產生的資源式路由。還可以新增更多集合路由、成員路由。
2.10.1 新增成員路由
要新增成員路由,只需要在 resources
區塊裡加入 member
區塊:
resources :photos do member do get 'preview' end end
這會識別出 /photos/1/preview
的 GET 請求,交給 PhotosController
的 preview
動作處理,相片的 ID 會存在 params[:id]
。也會新增 preview_photo_path
與 preview_photo_url
輔助方法。
在成員路由的區塊裡,可以指定使用特定的 HTTP 動詞。可用的有:get
、patch
、put
、post
或 delete
。若成員路由只有一筆,可以使用 :on
,便不需要以區塊形式宣告:
resources :photos do get 'preview', on: :member end
也可以不使用 :on
選項,會得到相同的成員路由,只是 ID 會存在 params[:photo_id]
,而不是 params[:id]
。
2.10.2 新增集合路由
新增一筆集合路由:
resources :photos do collection do get 'search' end end
這會使 Rails 識別出發送到 /photos/search
的 GET 請求,交給 PhotosController
的 search
動作處理。也會新增 search_photos_path
與 search_photos_url
輔助方法。
和成員路由相同,可以傳入 :on
選項:
resources :photos do get 'search', on: :collection end
2.10.3 給額外的新動作新增路由
要新增額外的 new
動作,可以使用 :on
選項:
resources :comments do get 'preview', on: :new end
這會使 Rails 識別出發送到 /comments/new/preview
的 GET 請求,交給 CommentsController
的 preview
動作處理。也會新增 preview_new_comment_path
與 preview_new_comment_url
輔助方法。
若發現給資源新增了許多額外的動作,停下來想想是不是要拆成另一個資源。
3 非資源式路由
除了資源式路由之外,將隨意的 URL 對應到動作,Rails 提供了強大的支持。這一節不像資源式路由,會獲得一組自動產生的路由。反而是自己在應用程式裡設定每一條路由。
雖然通常應該要使用資源式路由,但仍有許多簡單的路由更合適的場景。也不用整個應用程式都得用資源式風格的路由才行,選擇最合適的解決方案。
簡單的路由使得把傳統的 URL 對應到 Rails 動作變得特別簡單。
3.1 綁定參數
設定一般的路由時,提供一系列的符號給 Rails,Rails 會根據這些符號來對進來的 HTTP 請求做匹配。有兩個特殊符號::controller
會對應到應用程式裡的 Controller 名稱,而 :action
則是對應到該 Controller 的動作。舉例來說,看下面這條路由:
get ':controller(/:action(/:id))'
若發送到 /photos/show/1
的請求由這條路由處理(路由檔案裡沒有其它匹配的路由),則會呼叫 PhotosController
的 show
動作,並將 params{:id]
設為 "1"
。這條路由也會處理發送到 /photos
的請求,將請求交給 PhotosController#index
處理。因為 :action
、:id
放在括號裡代表是可選參數。
3.2 動態片段
一般的路由裡可以設定多個動態片段。路由裡任何不是 :controller
、:action
的選項,都會變成 params
的一部分。若有以下路由:
get ':controller/:action/:id/:user_id'
任何至 /photos/show/1/2
的請求會被分配給 PhotosController
的 show
動作處理,params[:id]
則會設為 "1"
,而 params[:user_id]
則是 "2"
。
路徑片段有 :controller
時,無法與 :namespace
、:module
一起使用。若需要這麼做的話,對 :controller
使用約束條件,明確指定要匹配的命名空間,譬如:
get ':controller(/:action(/:id))', controller: /admin\/[^\/]+/
動態片段預設不接受 .
── 這是因為 .
是格式化路由的分隔符。如需要在動態片段裡使用 .
,用約束條件來處理 ── 例如,id: /[^\/]+/
允許斜線以外的所有字元。
3.3 靜態片段
建立路由時,可以指定靜態片段,片段前不加冒號即可:
get ':controller/:action/:id/with_user/:user_id'
這條路由會回應發送到 /photos/show/1/with_user/2
的請求,params
的內容則為 { controller: 'photos', action: 'show', id: '1', user_id: '2' }
。
3.4 查詢字串
params
也會存放查詢字串的參數。舉個例子,看看以下這條路由:
get ':controller/:action/:id'
從 /photos/show/1?user_id=2
進來的請求會分配給 PhotosController
的 show
動作處理,params
的內容為 { controller: 'photos', action: 'show', id: '1', user_id: '2' }
。
3.5 定義預設值
路由裡不需要明確使用 :controller
與 :action
。可以指定預設值:
get 'photos/:id', to: 'photos#show'
有了這條路由之後,Rails 會分配 /photos/12
給 PhotosController
的 show
動作。
也可以傳 Hash 給 :defaults
選項,來給路由新增預設值。這對於沒有寫動態片段的路由也適用。舉例來說:
get 'photos/:id', to: 'photos#show', defaults: { format: 'jpg' }
Rails 會匹配 photos/12
to the show
action of PhotosController
, and set params[:format]
to "jpg"
.
3.6 命名路由
可以使用 :as
選項給任何路由取名字:
get 'exit', to: 'sessions#destroy', as: :logout
會建立出 logout_path
與 logout_url
這兩個具名輔助方法。呼叫 logout_path
會回傳 /exit
。
也可以使用 :as
來覆寫 resources
預設定義的方法:
get ':username', to: 'users#show', as: :user
會定義 user_path
方法,在 Controller、輔助方法、以及 View 裡都可用,會回傳像是 /bob
的路徑。在 UsersController
的 show
動作裡,params[:username]
會有使用者的 username
,如不喜歡參數名稱取名為 :username
可以修改這個值。
3.7 HTTP 動詞約束條件
通常應該使用 get
、post
、put
、patch
以及 delete
方法來限制路由只處理特定的動詞。match
方法與 :via
選項可以一直匹配多個動詞:
match 'photos', to: 'photos#show', via: [:get, :post]
路由要匹配所有的動詞也可以,via: :all
:
match 'photos', to: 'photos#show', via: :all
將 GET
與 POST
請求路由到單一的動作有安全隱憂。除非有很好的理由,通常應該要避免將所有 HTTP 動詞對應到一個動作上。
Rails 裡執行 GET
不會檢查 CSRF token。永遠不要讓 GET
請求可以執行資料庫寫入動作,更多資訊請參考《安全指南》 關於 〈CSRF countermeasures〉 一節。
3.8 片段約束
使用 :constraints
選項限制動態片段的格式:
get 'photos/:id', to: 'photos#show', constraints: { id: /[A-Z]\d{5}/ }
這條路由會匹配像是 /photos/A12345
,但不會匹配 /photos/893
。可以進一步簡化為:
get 'photos/:id', to: 'photos#show', id: /[A-Z]\d{5}/
:constraints
雖然接受正規表示法,但不能使用錨點(anchors)。比如以下路由不會正常工作:
get '/:id', to: 'articles#show', constraints: {id: /^\d/}
但其實不需要使用錨點,因為所有的路由皆從頭開始匹配。
舉個例子,下面的路由,若 articles
呼叫 to_param
的值像是 1-hello-world
,以數字開頭,就會把請求交給 ArticlesController
的 show
動作處理;而 to_param
的值不以數字開頭,像是 david
,則會交給 UsersController
的 show
動作處理。
get '/:id', to: 'articles#show', constraints: { id: /\d.+/ } get '/:username', to: 'users#show'
3.9 基於請求的約束條件
也可以使用 request
物件裡,任何會回傳字串的方法,來宣告路由的約束條件。
基於 request
物件的約束條件的宣告方式與片段約束條件相同:
get 'photos', to: 'photos#index', constraints: { subdomain: 'admin' }
約束條件也可以使用區塊形式:
namespace :admin do constraints subdomain: 'admin' do resources :photos end end
3.10 進階約束條件
如有更進階的約束條件,可以傳入一個回應 matches?
的物件。假設需要將所有黑名單的使用者交給 BlackListController
處理,可以這麼做:
class BlacklistConstraint def initialize @ips = Blacklist.retrieve_ips end def matches?(request) @ips.include?(request.remote_ip) end end Rails.application.routes.draw do get '*path', to: 'blacklist#index', constraints: BlacklistConstraint.new end
約束條件也可用 lambda 宣告:
Rails.application.routes.draw do get '*path', to: 'blacklist#index', constraints: lambda { |request| Blacklist.retrieve_ips.include?(request.remote_ip) } end
matches?
方法與 lambda 都接受 request
物件作為參數。
3.11 路由通配片段
路由通配是指定匹配參數的方法,比如:
get 'photos/*other', to: 'photos#unknown'
這筆路由會匹配 photos/12
或 /photos/long/path/to/12
,並將 params[:other]
設為 "12"
或 "long/path/to/12"
。有星號前綴的片段稱之為“通配片段”。
通配片段可在路由的任何位置出現,譬如:
get 'books/*section/:title', to: 'books#show'
會匹配 books/some/section/last-words-a-memoir
,params[:section]
會設為 "some/section"
,而 params[:title]
則是 "last-words-a-memoir"
。
技術上來說,路由可有多個通配片段。匹配器會以直觀的方式來將參數賦值給片段,比如:
get '*a/foo/*b', to: 'test#index'
會匹配 zoo/woo/foo/bar/baz
,params[:a]
會設為 'zoo/woo'
,而 params[:b]
則是 'bar/baz'
。
發送請求到 "/foo/bar.json"
時,params[:pages]
會設為 "foo/bar"
,請求格式為 JSON。若想使用 3.0.x 的行為,可以傳一個 format: false
:
get '*pages', to: 'pages#show', format: false
若想要設定格式為必要參數,則可以傳 format: true
:
get '*pages', to: 'pages#show', format: true
3.12 轉址
可以使用 redirect
輔助方法將甲路徑轉到乙路徑:
get '/stories', to: redirect('/articles')
轉址也可以重複使用匹配路由的動態片段:
get '/stories/:name', to: redirect('/articles/%{name}')
redirect
也可以以區塊形式定義,接受 path
參數與 request
物件:
get '/stories/:name', to: redirect { |path_params, req| "/articles/#{path_params[:name].pluralize}" } get '/stories', to: redirect { |path_params, req| "/articles/#{req.subdomain}" }
Note: 轉址是 301 "Moved Permanently" 轉址。某些瀏覽器或代理伺服器會快取 301 轉址,導致舊的頁面無法存取。
以上所有的情況裡,若沒有提供主機(http://www.example.com
),Rails 會從當下的請求裡取得。
3.13 路由到 Rack 應用程式
除了使用像是 "articles#index"
的字串(會交給 ArticlesController
的 index
動作處理),還可以指定任何 Rack 應用程式 作為端點(Endpoint):
match '/application.js', to: Sprockets, via: :all
只要 Sprockets
有回應 call
,並回傳 [status, headers, body]
,則路由器便不管這是一個 Rack 應用程式,還是單純一個動作。這是個應用 via: :all
的適當場景,因為希望 Rack 應用程式自己處理所有的 HTTP 動詞。
針對比較好奇的朋友,"articles#index
其實會展開成 ArticlesController.action(:index)
,會回傳一個合法的 Rack 應用程式。
3.14 使用 root
可以用 root
指定 Rails 要把 "/"
路由到那裡:
root to: 'pages#main' root 'pages#main' # 上例的縮寫
root
路由應放在 config/routes.rb
的最上方,因為這是最重要的路由,應該第一個匹配。
root
路由只處理對應動作的 GET
請求。
root
也可以在命名空間或是作用域裡使用:
namespace :admin do root to: "admin#index" end root to: "home#index"
3.15 Unicode 字元的路由
路由中可以直接指定 Unicode 字元:
get 'こんにちは', to: 'welcome#index'
4 客製化資源式路由
resources :articles
產生的預設路由與輔助方法通常可以滿足多數需求,但有時可能想在某種程度上進行客製化。Rails 允許資源式輔助方法的通用部分做客製化。
4.1 指定使用的 Controller
:controller
選項可明確指定資源要使用的 Controller:
resources :photos, controller: 'images'
會識別出以 /photos
開頭的請求,交給 Images
Controller 處理:
HTTP 動詞 | 路徑 | Controller#動作 | 具名輔助方法 |
---|---|---|---|
GET | /photos | images#index | photos_path |
GET | /photos/new | images#new | new_photo_path |
POST | /photos | images#create | photos_path |
GET | /photos/:id | images#show | photo_path(:id) |
GET | /photos/:id/edit | images#edit | edit_photo_path(:id) |
PATCH/PUT | /photos/:id | images#update | photo_path(:id) |
DELETE | /photos/:id | images#destroy | photo_path(:id) |
產生路徑的輔助方法保持不變 photos_path
、new_photo_path
。
具有命名空間的 Controller,指定時使用和目錄一樣的表示法即可:
resources :user_permissions, controller: 'admin/user_permissions'
會把路由交給 Admin::UserPermissions
處理。
只支援目錄表示法。使用 Ruby 的常數表示法(如:controller: "Admin::UserPermissions"
)會導致路由問題,並觸發警告。
4.2 指定約束條件
可用 :constraints
選項來對 id
指定格式,譬如:
resources :photos, constraints: { id: /[A-Z][A-Z][0-9]+/ }
這條宣告限制 :id
參數需符合指定的正規表示法。故這個情況裡,路由器不會匹配 /photos/1
,而是會匹配 /photos/RR27
。
可以將約束條件應用至多條路由,使用以下的區塊形式:
constraints(id: /[A-Z][A-Z][0-9]+/) do resources :photos resources :accounts end
當然這裡也可以使用非資源式路由的進階約束條件。
:id
預設不接受 .
── 這是因為 .
是格式化路由的分隔符。如需要在 :id
裡使用 .
,用約束條件來處理 ── 例如,id: /[^\/]+/
允許斜線以外的所有字元。
4.3 覆寫具名輔助方法
:as
選項可以覆寫具名輔助方法的名稱。比如:
resources :photos, as: 'images'
will recognize incoming paths beginning with /photos
and route the requests to PhotosController
, but use the value of the :as option to name the helpers.
HTTP 動詞 | 路徑 | Controller#動作 | 具名輔助方法 |
---|---|---|---|
GET | /photos | photos#index | images_path |
GET | /photos/new | photos#new | new_image_path |
POST | /photos | photos#create | images_path |
GET | /photos/:id | photos#show | image_path(:id) |
GET | /photos/:id/edit | photos#edit | edit_image_path(:id) |
PATCH/PUT | /photos/:id | photos#update | image_path(:id) |
DELETE | /photos/:id | photos#destroy | image_path(:id) |
4.4 覆寫 new
與 edit
片段
:path_names
選項可以覆寫自動在路徑裡產生的 new
與 edit
片段:
resources :photos, path_names: { new: 'make', edit: 'change' }
路由路徑變更為:
/photos/make /photos/1/change
實際的動作名稱並沒有改變。這兩個路徑仍路由到 new
and edit
動作。
若發現一直使用 :path_names
的話,考慮使用 scope
。
scope path_names: { new: 'make' } do # rest of your routes end
4.5 給具名輔助方法加前綴
:as
選項可以給具名輔助方法加上前綴。使用此選項用來避免路由之間的命名衝突,例如:
scope 'admin' do resources :photos, as: 'admin_photos' end resources :photos
會產生像是 admin_photos_path
、new_admin_photo_path
等路由輔助方法。
要前綴一組路由輔助方法,使用 scope
與 :as
選項:
scope 'admin', as: 'admin' do resources :photos, :accounts end resources :photos, :accounts
這會產生像是 admin_photos_path
以及 admin_accounts_path
輔助方法,分別對應到 /admin/photos
以及 /admin/accounts
。
namespace
作用域會自動新增 :as
、:module
以及 :path
前綴。
也可以使用具名參數給路由加上前綴:
scope ':username' do resources :articles end
這會產生像是 /bob/articles/1
的路由,並允許在 Controller、View 以及輔助方法使用 params[:username]
來存取路徑傳入的 username
。
4.6 限制建立出來的路由
Rails 預設會為以資源式風格宣告的路由的七個動作(index、show、new、create、edit、update 以及 destroy)建立路由。可用 :only
以及 :except
選項來調整這個行為。:only
選項告訴 Rails 只需要產生那些路由就好:
resources :photos, only: [:index, :show]
/photos
收到 GET 請求會正常工作,但 POST 請求(通常會路由到 :create
動作)則會失敗。
The :except
選項指定不要建立的路由:
resources :photos, except: :destroy
這個情況 Rails 會建立出除了 :destory
(發送 DELETE
請求到 /photos/:id
)之外所有的路由。
如應用程式有許多 RESTful 風格的路由,使用 :only
與 :except
選項來產生需要的路由。這樣可以減少記憶體的使用量,並加速路由過程。
4.7 翻譯路徑
使用 scope
,可以修改由 resources
產生的路徑名稱:
scope(path_names: { new: 'neu', edit: 'bearbeiten' }) do resources :categories, path: 'kategorien' end
產生的路由:
HTTP 動詞 | 路徑 | Controller#動作 | 具名輔助方法 |
---|---|---|---|
GET | /kategorien | categories#index | categories_path |
GET | /kategorien/neu | categories#new | new_category_path |
POST | /kategorien | categories#create | categories_path |
GET | /kategorien/:id | categories#show | category_path(:id) |
GET | /kategorien/:id/bearbeiten | categories#edit | edit_category_path(:id) |
PATCH/PUT | /kategorien/:id | categories#update | category_path(:id) |
DELETE | /kategorien/:id | categories#destroy | category_path(:id) |
4.8 覆寫單數形式
若想定義某個資源的單數形式,給 Inflector
新增額外的規則:
ActiveSupport::Inflector.inflections do |inflect| inflect.irregular 'tooth', 'teeth' end
4.9 在嵌套資源中使用 :as
選項
:as
選項覆寫在嵌套資源裡,自動產生的路由輔助方法,例如:
resources :magazines do resources :ads, as: 'periodical_ads' end
這會產生出像是:edit_magazine_periodical_ad_path
與 magazine_periodical_ads_url
等輔助方法。
4.10 覆寫具名路由參數
:param
選項可以覆寫辨識資源的變數,預設是 :id
(動態片段的名稱)。這個變數是用來產生路由的。可以在 Controller 使用 params[<:param>]
傳入片段變數。
resources :videos, param: :identifier
videos GET /videos(.:format) videos#index POST /videos(.:format) videos#create new_videos GET /videos/new(.:format) videos#new edit_videos GET /videos/:identifier/edit(.:format) videos#edit
Video.find_by(identifier: params[:identifier])
5 檢查與測試路由
Rails 有提供用來檢查與測試路由的工具。
5.1 列出現有的路由
要獲得應用程式完整可用的路由列表,在開發環境下啟動伺服器,瀏覽 http://localhost:3000/rails/info/routes
。也可以在終端機裡執行 rake routes
命令。
以上兩種方法都會列出所有的路由,順序與 config/routes.rb
裡定義的一樣,每條路由的內容會有:
- 路由名稱(有的話)
- 使用的 HTTP 動詞(若不是回應所有動詞的路由)
- 匹配的 URL 模式。
- 路由的參數。
舉個例子,以下是某個 RESTful 路由的 rake routes
輸出的一小部分:
users GET /users(.:format) users#index POST /users(.:format) users#create new_user GET /users/new(.:format) users#new edit_user GET /users/:id/edit(.:format) users#edit
可以使用環境變數 CONTROLLER
,來限制僅列出特定 Controller 的路由:
$ CONTROLLER=users rake routes
若將終端機視窗拉寬到無斷行,會發現 rake routes
輸出的可讀性更高。
5.2 測試路由
路由和其它部分的程式一樣,也需要測試。Rails 提供三個內建的斷言方法,專門為簡化路由測試而設計:
assert_generates
assert_recognizes
assert_routing
5.2.1 assert_generates
斷言
assert_generates
檢測給定選項產生出來的預設路由或自訂路由是否正確:
assert_generates '/photos/1', { controller: 'photos', action: 'show', id: '1' } assert_generates '/about', controller: 'pages', action: 'about'
5.2.2 assert_recognizes
斷言
assert_recognizes
與 assert_generates
相反。檢測給定的路徑是否能識別出來,並路由到正確的位置。比如:
assert_recognizes({ controller: 'photos', action: 'show', id: '1' }, '/photos/1')
可用 :method
參數來指定 HTTP 動詞:
assert_recognizes({ controller: 'photos', action: 'create' }, { path: 'photos', method: :post })
5.2.3 assert_routing
斷言
assert_routing
斷言雙向檢查路由:測試路徑產生的選項對不對,以及選項產生出來的路徑是否正確。因此,結合了 assert_generates
與 assert_recognizes
的功能:
assert_routing({ path: 'photos', method: :post }, { controller: 'photos', action: 'create' })
反饋
歡迎幫忙改善指南的品質。
如發現任何錯誤之處,歡迎修正。開始貢獻前,可以先閱讀貢獻指南:文件。
翻譯如有錯誤,深感抱歉,歡迎 Fork 修正,或至此處回報。
文章可能有未完成或過時的內容。請先檢查 Edge Guides 來確定問題在 master 是否已經修掉了。再上 master 補上缺少的文件。內容參考 Ruby on Rails 指南準則來了解行文風格。
最後,任何關於 Ruby on Rails 文件的討論,歡迎至 rubyonrails-docs 郵件論壇。