1 Rails 路由器的目的
Rails 路由器識別 URL,並將它們分派到控制器的動作或 Rack 應用程式。它也可以產生路徑和 URL,避免在檢視中硬編碼字串。
1.1 將 URL 連接到程式碼
當你的 Rails 應用程式收到一個針對
GET /patients/17
的輸入請求時,它會請路由器將其與控制器動作配對。如果第一個配對路由是
get '/patients/:id', to: 'patients#show'
請求會分派到 patients
控制器的 show
動作,並在 params
中傳遞 { id: '17' }
。
Rails 在這裡使用 snake_case 作為控制器名稱,如果你有一個多字元的控制器,例如 MonsterTrucksController
,則需要使用 monster_trucks#show
。
1.2 從程式碼產生路徑和 URL
您也可以產生路徑和 URL。如果將上方的路由修改為
get '/patients/:id', to: 'patients#show', as: 'patient'
且您的應用程式在控制器中包含這段程式碼
@patient = Patient.find(params[:id])
以及在對應的檢視中包含這段程式碼
<%= link_to 'Patient Record', patient_path(@patient) %>
那麼路由器將會產生路徑 /patients/17
。這會降低檢視的脆弱性,讓您的程式碼更容易理解。請注意,在路由輔助程式中不需要指定 id。
1.3 設定 Rails 路由器
應用程式或引擎的路由位於檔案 config/routes.rb
中,通常如下所示
Rails.application.routes.draw do
resources :brands, only: [:index, :show] do
resources :products, only: [:index, :show]
end
resource :basket, only: [:show, :update, :destroy]
resolve("Basket") { route_for(:basket) }
end
由於這是一個正規的 Ruby 原始檔,您可以使用其所有功能來協助您定義路由,但請小心變數名稱,因為它們可能會與路由器的 DSL 方法發生衝突。
包含路由定義的區塊 Rails.application.routes.draw do ... end
是建立路由器 DSL 範圍所必需的,且不得刪除。
2 資源路由:Rails 預設
資源路由讓您可以快速宣告給定資源控制器的所有常見路由。單一呼叫 resources
可以宣告 index
、show
、new
、edit
、create
、update
和 destroy
動作所需的所有路由。
2.1 網路上的資源
瀏覽器透過使用特定 HTTP 方法(例如 GET
、POST
、PATCH
、PUT
和 DELETE
)對 URL 發出要求,從 Rails 要求頁面。每個方法都是對資源執行操作的要求。資源路由會將許多相關要求對應到單一控制器中的動作。
當你的 Rails 應用程式收到一個針對
DELETE /photos/17
它要求路由器將其對應到控制器動作。如果第一個相符路由是
resources :photos
Rails 會將該要求傳送給 photos
控制器上的 destroy
動作,其中 params
中的 { id: '17' }
。
2.2 CRUD、動詞和動作
在 Rails 中,足夠的路由會提供 HTTP 動詞和 URL 與控制器動作之間的對應。根據慣例,每個動作也會對應到資料庫中的特定 CRUD 操作。路由檔案中的單一項目,例如
resources :photos
會在應用程式中建立七個不同的路由,全部對應到 Photos
控制器
HTTP 動詞 | 路徑 | 控制器#動作 | 用於 |
---|---|---|---|
GET | /photos | photos#index | 顯示所有照片的清單 |
GET | /photos/new | photos#new | 傳回用於建立新照片的 HTML 表單 |
POST | /photos | photos#create | 建立新照片 |
GET | /photos/:id | photos#show | 顯示特定照片 |
GET | /photos/:id/edit | photos#edit | 傳回用於編輯照片的 HTML 表單 |
PATCH/PUT | /photos/:id | photos#update | 更新特定照片 |
DELETE | /photos/:id | photos#destroy | 刪除特定照片 |
由於路由器使用 HTTP 動詞和 URL 來比對內部要求,因此四個 URL 會對應到七個不同的動作。
Rails 路由會按照指定的順序進行比對,因此如果在 get 'photos/poll'
上方有一個 resources :photos
,則 resources
行的 show
動作路由會在 get
行之前進行比對。若要修正此問題,請將 get
行移到 resources
行的上方,以便優先進行比對。
2.3 路徑和 URL 輔助程式
建立豐富的路由也會對應用程式中的控制器公開許多輔助程式。在 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
),傳回相同路徑,並加上目前的 host、port 和路徑字首。
若要找出路由的路由輔助程式名稱,請參閱下方的 列出現有路由。
2.4 同時定義多個資源
如果您需要為多個資源建立路由,您可以透過使用單一呼叫 resources
來定義所有資源,以節省一些輸入。
resources :photos, :books, :videos
這與下列方式完全相同
resources :photos
resources :books
resources :videos
2.5 單數資源
有時,您有一個資源,客戶總是在不參照 ID 的情況下查詢。例如,您希望 /profile
始終顯示目前已登入使用者的個人資料。在這種情況下,您可以使用單數資源將 /profile
(而不是 /profile/:id
)對應到 show
動作
get 'profile', to: 'users#show'
傳遞 String
給 to:
會期待 controller#action
格式。使用 Symbol
時,應將 to:
選項替換為 action:
。使用不含 #
的 String
時,應將 to:
選項替換為 controller:
get 'profile', action: :show, controller: 'users'
這個有用的路由
resource :geocoder
resolve('Geocoder') { [:geocoder] }
在您的應用程式中建立六個不同的路由,全部對應到 Geocoders
控制器
HTTP 動詞 | 路徑 | 控制器#動作 | 用於 |
---|---|---|---|
GET | /geocoder/new | geocoders#new | 傳回一個 HTML 表單,用於建立地理編碼器 |
POST | /geocoder | geocoders#create | 建立新的地理編碼器 |
GET | /geocoder | geocoders#show | 顯示唯一一個地理編碼器資源 |
GET | /geocoder/edit | geocoders#edit | 傳回一個 HTML 表單,用於編輯地理編碼器 |
PATCH/PUT | /geocoder | geocoders#update | 更新唯一一個地理編碼器資源 |
DELETE | /geocoder | geocoders#destroy | 刪除地理編碼器資源 |
由於您可能想要對單數路由 (/account
) 和複數路由 (/accounts/45
) 使用同一個控制器,因此單數資源會對應到複數控制器。因此,例如,resource :photo
和 resources :photos
會建立單數和複數路由,這些路由會對應到同一個控制器 (PhotosController
)。
單數有用的路由會產生這些輔助函式
new_geocoder_path
傳回/geocoder/new
edit_geocoder_path
傳回/geocoder/edit
geocoder_path
傳回/geocoder
呼叫 resolve
是必要的,用於透過 記錄識別 將 Geocoder
的執行個體轉換為路由。
與複數資源一樣,以 _url
結尾的相同輔助函式也會包含主機、埠和路徑前置詞。
2.6 控制器命名空間和路由
您可能希望在命名空間下組織控制器群組。最常見的是,您可能會將許多管理控制器群組在 Admin::
命名空間下,並將這些控制器放置在 app/controllers/admin
目錄下。您可以透過使用 namespace
區塊來路由到此類群組
namespace :admin do
resources :articles, :comments
end
這將為每個 articles
和 comments
控制器建立多個路由。對於 Admin::ArticlesController
,Rails 將建立
HTTP 動詞 | 路徑 | 控制器#動作 | 命名路由輔助函式 |
---|---|---|---|
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) |
如果您希望將 /articles
(沒有前置詞 /admin
)路由到 Admin::ArticlesController
,您可以使用 scope
區塊指定模組
scope module: 'admin' do
resources :articles, :comments
end
這也可以針對單一路由執行
resources :articles, module: 'admin'
如果您希望將 /admin/articles
路由到 ArticlesController
(沒有 Admin::
模組前置詞),您可以使用 scope
區塊指定路徑
scope '/admin' do
resources :articles, :comments
end
這也可以針對單一路由執行
resources :articles, path: '/admin/articles'
在這些情況下,命名路由輔助函式與您沒有使用 scope
時相同。在最後一個情況下,下列路徑會對應到 ArticlesController
HTTP 動詞 | 路徑 | 控制器#動作 | 命名路由輔助函式 |
---|---|---|---|
GET | /admin/articles | articles#index | articles_path |
GET | /admin/articles/new | articles#new | new_article_path |
POST | /admin/articles | 文章#建立 | articles_path |
GET | /admin/articles/:id | 文章#顯示 | article_path(:id) |
GET | /admin/articles/:id/edit | 文章#編輯 | edit_article_path(:id) |
PATCH/PUT | /admin/articles/:id | 文章#更新 | article_path(:id) |
DELETE | /admin/articles/:id | 文章#刪除 | article_path(:id) |
如果您需要在 namespace
區塊內使用不同的控制器命名空間,您可以指定絕對控制器路徑,例如:get '/foo', to: '/foo#index'
。
2.7 巢狀資源
資源通常是其他資源的邏輯子項。例如,假設您的應用程式包含這些模型
class Magazine < ApplicationRecord
has_many :ads
end
class Ad < ApplicationRecord
belongs_to :magazine
end
巢狀路由讓您在路由中擷取此關係。在此情況下,您可以包含此路由宣告
resources :magazines do
resources :ads
end
除了雜誌的路由之外,此宣告也會將廣告路由到 AdsController
。廣告網址需要雜誌
HTTP 動詞 | 路徑 | 控制器#動作 | 用於 |
---|---|---|---|
GET | /magazines/:magazine_id/ads | ads#index | 顯示特定雜誌的所有廣告清單 |
GET | /magazines/:magazine_id/ads/new | ads#new | 傳回建立屬於特定雜誌的新廣告的 HTML 表單 |
POST | /magazines/:magazine_id/ads | ads#create | 建立屬於特定雜誌的新廣告 |
GET | /magazines/:magazine_id/ads/:id | ads#show | 顯示屬於特定雜誌的特定廣告 |
GET | /magazines/:magazine_id/ads/:id/edit | ads#edit | 傳回編輯屬於特定雜誌的廣告的 HTML 表單 |
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_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 淺層巢狀
避免深度巢狀的一種方法(如上所述)是在父項下產生範圍化的集合動作,以了解階層,但不要巢狀成員動作。換句話說,只建立具有最少資訊來唯一識別資源的路由,如下所示
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
這裡的文章資源將產生下列路由
HTTP 動詞 | 路徑 | 控制器#動作 | 命名路由輔助函式 |
---|---|---|---|
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_comment_path |
GET | /comments/:id(.:format) | comments#show | comment_path |
PATCH/PUT | /comments/:id(.:format) | comments#update | comment_path |
DELETE | /comments/:id(.:format) | comments#destroy | comment_path |
GET | /articles/:article_id/quotes(.:format) | quotes#index | article_quotes_path |
POST | /articles/:article_id/quotes(.:format) | quotes#create | article_quotes_path |
GET | /articles/:article_id/quotes/new(.:format) | quotes#new | new_article_quote_path |
GET | /quotes/:id/edit(.:format) | quotes#edit | edit_quote_path |
GET | /quotes/:id(.:format) | 引號#顯示 | 引號路徑 |
PATCH/PUT | /quotes/:id(.:format) | 引號#更新 | 引號路徑 |
DELETE | /quotes/:id(.:format) | 引號#銷毀 | 引號路徑 |
GET | /文章/:文章_id/草稿(.:格式) | 草稿#索引 | 文章草稿路徑 |
POST | /文章/:文章_id/草稿(.:格式) | 草稿#建立 | 文章草稿路徑 |
GET | /文章/:文章_id/草稿/新增(.:格式) | 草稿#新增 | 新增文章草稿路徑 |
GET | /草稿/:id/編輯(.:格式) | 草稿#編輯 | 編輯草稿路徑 |
GET | /草稿/:id(.:格式) | 草稿#顯示 | 草稿路徑 |
PATCH/PUT | /草稿/:id(.:格式) | 草稿#更新 | 草稿路徑 |
DELETE | /草稿/:id(.:格式) | 草稿#銷毀 | 草稿路徑 |
GET | /文章(.:格式) | articles#index | articles_path |
POST | /文章(.:格式) | 文章#建立 | articles_path |
GET | /文章/新增(.:格式) | articles#new | new_article_path |
GET | /文章/:id/編輯(.:格式) | 文章#編輯 | 編輯文章路徑 |
GET | /文章/:id(.:格式) | 文章#顯示 | 文章路徑 |
PATCH/PUT | /文章/:id(.:格式) | 文章#更新 | 文章路徑 |
DELETE | /文章/:id(.:格式) | 文章#刪除 | 文章路徑 |
DSL 的 shallow
方法會建立一個範圍,在範圍內每個巢狀結構都是淺層的。這會產生與前一個範例相同的路由
shallow do
resources :articles do
resources :comments
resources :quotes
resources :drafts
end
end
scope
有兩個選項可以自訂淺層路由。:shallow_path
會在成員路徑前加上指定的參數
scope shallow_path: "sekret" do
resources :articles do
resources :comments, shallow: true
end
end
這裡的留言資源會產生下列路由
HTTP 動詞 | 路徑 | 控制器#動作 | 命名路由輔助函式 |
---|---|---|---|
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/留言/:id/編輯(.:格式) | comments#edit | edit_comment_path |
GET | /sekret/留言/:id(.:格式) | comments#show | comment_path |
PATCH/PUT | /sekret/留言/:id(.:格式) | comments#update | comment_path |
DELETE | /sekret/留言/:id(.:格式) | comments#destroy | comment_path |
:shallow_prefix
選項會將指定的參數新增到命名路由輔助程式
scope shallow_prefix: "sekret" do
resources :articles do
resources :comments, shallow: true
end
end
這裡的留言資源會產生下列路由
HTTP 動詞 | 路徑 | 控制器#動作 | 命名路由輔助函式 |
---|---|---|---|
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 | 編輯_sekret_留言路徑 |
GET | /comments/:id(.:format) | comments#show | sekret_留言路徑 |
PATCH/PUT | /comments/:id(.:format) | comments#update | sekret_留言路徑 |
DELETE | /comments/:id(.:format) | comments#destroy | sekret_留言路徑 |
2.8 路由疑慮
路由疑慮允許您宣告可以在其他資源和路由中重複使用的共用路由。若要定義疑慮,請使用 concern
區塊
concern :commentable do
resources :comments
end
concern :image_attachable do
resources :images, only: :index
end
這些疑慮可以用在資源中,以避免重複撰寫程式碼和在路由間共用行為
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
在任何地方使用它們。例如,在 scope
或 namespace
區塊中
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
是 Magazine
,而 @ad
是 Ad
,因此會使用 magazine_ad_path
輔助程式。在像 link_to
這樣的輔助程式中,您可以只指定物件,取代完整的 url_for
呼叫
<%= link_to 'Ad details', [@magazine, @ad] %>
如果您只想連結到一本雜誌
<%= link_to 'Magazine details', @magazine %>
對於其他動作,您只需要將動作名稱插入陣列的第一個元素
<%= link_to 'Edit Ad', [:edit, @magazine, @ad] %>
這讓您可以將模型的執行個體視為 URL,是使用資源風格的一個主要優點。
2.10 加入更多 RESTful 動作
您不限於 RESTful 路由預設建立的七個路由。如果您喜歡,您可以加入適用於集合或集合中個別成員的其他路由。
2.10.1 加入成員路由
若要加入成員路由,只要在資源區塊中加入一個 member
區塊
resources :photos do
member do
get 'preview'
end
end
這會以 GET 識別 /photos/1/preview
,並路由到 PhotosController
的 preview
動作,其中資源 ID 值會傳遞到 params[:id]
。它也會建立 preview_photo_url
和 preview_photo_path
輔助程式。
在成員路由區塊中,每個路由名稱會指定會被識別的 HTTP 動詞。您可以在這裡使用 get
、patch
、put
、post
或 delete
。如果您沒有多個 member
路由,您也可以將 :on
傳遞給路由,消除區塊
resources :photos do
get 'preview', on: :member
end
您可以省略 :on
選項,這會建立相同的成員路由,但資源 ID 值會在 params[:photo_id]
中,而不是 params[:id]
。路由輔助程式也會從 preview_photo_url
和 preview_photo_path
重新命名為 photo_preview_url
和 photo_preview_path
。
2.10.2 新增集合路由
若要新增路由至集合,請使用 collection
區塊
resources :photos do
collection do
get 'search'
end
end
這將讓 Rails 能夠辨識路徑(例如 /photos/search
)並使用 GET,並將其路由至 PhotosController
的 search
動作。它也會建立 search_photos_url
和 search_photos_path
路由輔助程式。
就像成員路由一樣,你可以將 :on
傳遞至路由
resources :photos do
get 'search', on: :collection
end
如果你使用符號作為第一個位置引數來定義其他資源路由,請注意它不等於使用字串。符號會推論控制器動作,而字串會推論路徑。
2.10.3 新增其他新增動作的路由
若要使用 :on
捷徑新增替代新增動作
resources :comments do
get 'preview', on: :new
end
這將讓 Rails 能夠辨識路徑(例如 /comments/new/preview
)並使用 GET,並將其路由至 CommentsController
的 preview
動作。它也會建立 preview_new_comment_url
和 preview_new_comment_path
路由輔助程式。
如果你發現自己為有資源的路由新增許多額外動作,這時候就該停下來問自己是否在掩飾另一個資源的存在。
3 非資源路由
除了資源路由之外,Rails 還強而有力地支援將任意 URL 路由至動作。在這裡,你不會取得由有資源路由自動產生的路由群組。相反地,你會在應用程式中個別設定每個路由。
雖然你通常應該使用有資源路由,但仍有許多地方更適合使用較簡單的路由。如果你覺得不適合,就不用勉強將應用程式的每個部分都塞進有資源架構中。
特別是,簡單路由讓你可以非常輕鬆地將舊版 URL 對應至新的 Rails 動作。
3.1 繫結參數
當您設定常規路由時,您提供一系列 Rails 對應至 HTTP 要求中各部分的符號。例如,考慮此路由
get 'photos(/:id)', to: 'photos#display'
如果透過此路由處理 /photos/1
的 HTTP 要求(因為它未與檔案中任何先前的路由相符),結果將呼叫 PhotosController
的 display
動作,並將最終參數 "1"
作為 params[:id]
提供。此路由也會將 /photos
的 HTTP 要求路由至 PhotosController#display
,因為 :id
是由括號表示的選用參數。
3.2 動態區段
您可以在常規路由中設定任意數量的動態區段。任何區段都將作為 params
的一部分提供給動作。如果您設定此路由
get 'photos/:id/:user_id', to: 'photos#show'
/photos/1/2
的 HTTP 路徑將傳送至 PhotosController
的 show
動作。params[:id]
將為 "1"
,而 params[:user_id]
將為 "2"
。
預設情況下,動態區段不接受句點,這是因為句點用作格式化路由的分隔符號。如果您需要在動態區段中使用句點,請新增一個覆寫此限制的約束,例如,id: /[^\/]+/
允許任何內容,唯獨斜線除外。
3.3 靜態區段
您可以在建立路由時指定靜態區段,方法是不在區段前加上冒號
get 'photos/:id/with_user/:user_id', to: 'photos#show'
此路由會回應 /photos/1/with_user/2
等路徑。在此情況下,params
將為 { controller: 'photos', action: 'show', id: '1', user_id: '2' }
。
3.4 查詢字串
params
也會包含查詢字串中的任何參數。例如,使用這個路由
get 'photos/:id', to: 'photos#show'
/photos/1?user_id=2
的輸入路徑會傳遞到 Photos
控制器中的 show
動作。params
會是 { controller: 'photos', action: 'show', id: '1', user_id: '2' }
。
3.5 定義預設值
你可以透過提供 :defaults
選項的雜湊來定義路由中的預設值。這甚至適用於你未指定為動態區段的參數。例如
get 'photos/:id', to: 'photos#show', defaults: { format: 'jpg' }
Rails 會將 photos/12
與 PhotosController
的 show
動作配對,並將 params[:format]
設定為 "jpg"
。
你也可以使用 defaults
區塊來定義多個項目的預設值
defaults format: :json do
resources :photos
end
你無法透過查詢參數覆寫預設值 - 這是出於安全原因。唯一可以覆寫的預設值是透過 URL 路徑中的替換來動態區段。
3.6 命名路由
你可以使用 :as
選項為任何路由指定名稱
get 'exit', to: 'sessions#destroy', as: :logout
這會在你的應用程式中建立 logout_path
和 logout_url
作為已命名路由輔助程式。呼叫 logout_path
會傳回 /exit
你也可以使用它來覆寫資源定義的路由方法,方法是在定義資源之前放置自訂路由,如下所示
get ':username', to: 'users#show', as: :user
resources :users
這會定義一個 user_path
方法,該方法會在控制器、輔助程式和檢視中使用,並會前往類似 /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
要求都導向同一個動作會產生安全性問題。一般來說,除非有充分理由,否則您應該避免將所有動詞都導向同一個動作。
Rails 中的 GET
動作不會檢查 CSRF 令牌。您絕不應該透過 GET
要求寫入資料庫,如需更多資訊,請參閱 安全性指南 中關於 CSRF 對策的說明。
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
會採用正規表示式,但限制是不能使用 regexp 錨點。例如,下列路徑將無法運作
get '/:id', to: 'articles#show', constraints: { id: /^\d/ }
不過,請注意您不需要使用錨點,因為所有路徑在起點和終點都會錨定。
例如,下列路由允許 articles
的 to_param
值為 1-hello-world
,總是從數字開始,而 users
的 to_param
值為 david
,從不以數字開始,以共用根命名空間
get '/:id', to: 'articles#show', constraints: { id: /\d.+/ }
get '/:username', to: 'users#show'
3.9 基於請求的約束
您也可以根據 Request 物件 上任何傳回 String
的方法,來限制路由。
您指定基於請求的約束,方式與指定區段約束相同
get 'photos', to: 'photos#index', constraints: { subdomain: 'admin' }
您也可以使用 constraints
區塊來指定約束
namespace :admin do
constraints subdomain: 'admin' do
resources :photos
end
end
請求約束透過呼叫 Request 物件 上與雜湊金鑰名稱相同的方法,然後將傳回值與雜湊值進行比較,來運作。因此,約束值應與對應的 Request 物件方法傳回類型相符。例如:constraints: { subdomain: 'api' }
會如預期般符合 api
次網域。然而,使用符號 constraints: { subdomain: :api }
就不會,因為 request.subdomain
傳回 'api'
作為字串。
format
約束有一個例外:雖然它在 Request 物件上是一個方法,但它也是每個路徑上一個隱含的選用參數。區段約束優先,而 format
約束僅在透過雜湊函數強制執行時才會套用。例如,get 'foo', constraints: { format: 'json' }
會比對 GET /foo
,因為格式預設為選用。不過,你可以使用 lambda,例如 get 'foo', constraints: lambda { |req| req.format == :json }
,而路由只會比對明確的 JSON 要求。
3.10 進階約束
如果你有更進階的約束,你可以提供一個物件來回應 Rails 應使用的 matches?
。假設你想要將受限清單上的所有使用者路由到 RestrictedListController
。你可以這樣做
class RestrictedListConstraint
def initialize
@ips = RestrictedList.retrieve_ips
end
def matches?(request)
@ips.include?(request.remote_ip)
end
end
Rails.application.routes.draw do
get '*path', to: 'restricted_list#index',
constraints: RestrictedListConstraint.new
end
你也可以將約束指定為 lambda
Rails.application.routes.draw do
get '*path', to: 'restricted_list#index',
constraints: lambda { |request| RestrictedList.retrieve_ips.include?(request.remote_ip) }
end
matches?
方法和 lambda 都會將 request
物件作為引數。
3.10.1 區塊形式的約束
你可以以區塊形式指定約束。這在需要將相同規則套用至多個路由時很有用。例如
class RestrictedListConstraint
# ...Same as the example above
end
Rails.application.routes.draw do
constraints(RestrictedListConstraint.new) do
get '*path', to: 'restricted_list#index'
get '*other-path', to: 'other_restricted_list#index'
end
end
你也可以使用 lambda
Rails.application.routes.draw do
constraints(lambda { |request| RestrictedList.retrieve_ips.include?(request.remote_ip) }) do
get '*path', to: 'restricted_list#index'
get '*other-path', to: 'other_restricted_list#index'
end
end
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
,它會接收符號化的路徑參數和要求物件
get '/stories/:name', to: redirect { |path_params, req| "/articles/#{path_params[:name].pluralize}" }
get '/stories', to: redirect { |path_params, req| "/articles/#{req.subdomain}" }
請注意,預設重新導向為 301「已永久搬移」重新導向。請記住,某些網路瀏覽器或代理伺服器會快取這種類型的重新導向,讓舊頁面無法存取。您可以使用 :status
選項來變更回應狀態
get '/stories/:name', to: redirect('/articles/%{name}', status: 302)
在所有這些情況下,如果您沒有提供前導主機 (http://www.example.com
),Rails 會從目前的要求中取得那些詳細資料。
3.13 導向至 Rack 應用程式
與對應於 ArticlesController
中 index
動作的字串 'articles#index'
不同,您可以指定任何 Rack 應用程式 作為比對器的終端點
match '/application.js', to: MyRackApp, via: :all
只要 MyRackApp
會回應 call
並傳回 [status, headers, body]
,路由器就無法分辨 Rack 應用程式和動作之間的差異。這是適當使用 via: :all
的情況,因為您會希望讓您的 Rack 應用程式處理它認為適當的所有動詞。
對於好奇的人來說,'articles#index'
實際上會擴充為 ArticlesController.action(:index)
,它會傳回一個有效的 Rack 應用程式。
由於程序/lambda 是會回應 call
的物件,因此您可以內嵌實作非常簡單的路由(例如,用於健康檢查)get '/health', to: ->(env) { [204, {}, ['']] }
如果您指定 Rack 應用程式作為比對器的終端點,請記住路由在接收應用程式中不會變更。使用下列路由時,您的 Rack 應用程式應預期路由為 /admin
match '/admin', to: AdminApp, via: :all
如果您希望您的 Rack 應用程式在根路徑接收要求,請使用 mount
mount AdminApp, at: '/admin'
3.14 使用 root
您可以使用 root
方法指定 Rails 應將 '/'
路由到何處
root to: 'pages#main'
root 'pages#main' # shortcut for the above
您應將 root
路由放在檔案的最上方,因為它是最熱門的路由,應優先比對。
root
路由只會將 GET
要求路由到動作。
您也可以在命名空間和範圍內使用 root。例如
namespace :admin do
root to: "admin#index"
end
root to: "home#index"
3.15 Unicode 字元路由
您可以直接指定 unicode 字元路由。例如
get 'こんにちは', to: 'welcome#index'
3.16 直接路由
您可以透過呼叫 direct
直接建立自訂 URL 輔助程式。例如
direct :homepage do
"https://rubyonrails.org"
end
# >> homepage_url
# => "https://rubyonrails.org"
區塊的傳回值必須是 url_for
方法的有效引數。因此,您可以傳遞有效的字串 URL、雜湊、陣列、Active Model 實例或 Active Model 類別。
direct :commentable do |model|
[ model, anchor: model.dom_id ]
end
direct :main do
{ controller: 'pages', action: 'index', subdomain: 'www' }
end
3.17 使用 resolve
resolve
方法允許自訂模型的多型映射。例如
resource :basket
resolve("Basket") { [:basket] }
<%= form_with model: @basket do |form| %>
<!-- basket form -->
<% end %>
這將產生單數 URL /basket
,而非通常的 /baskets/:id
。
4 自訂資源路由
儘管由 resources
產生的預設路由和輔助程式通常都能順利運作,但您可能希望以某種方式自訂它們。Rails 允許您自訂資源輔助程式的幾乎所有一般部分。
4.1 指定要使用的控制器
:controller
選項讓您明確指定要為資源使用的控制器。例如
resources :photos, controller: 'images'
將辨識以 /photos
開頭的輸入路徑,但路由至 Images
控制器
HTTP 動詞 | 路徑 | 控制器#動作 | 命名路由輔助函式 |
---|---|---|---|
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
等來產生此資源的路徑。
對於命名空間控制器,您可以使用目錄表示法。例如
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'
將辨識以 /photos
開頭的輸入路徑,並將請求路由到 PhotosController
,但使用 :as
選項的值來命名輔助函式。
HTTP 動詞 | 路徑 | 控制器#動作 | 命名路由輔助函式 |
---|---|---|---|
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
和 edit
動作。
如果您發現自己想要針對所有路由統一變更此選項,您可以使用範圍,如下所示
scope path_names: { new: 'make' } do
# rest of your routes
end
4.5 為命名路由輔助函式加上前置字
您可以使用 :as
選項為 Rails 為路由產生的命名路由輔助函式加上前置字。使用此選項可防止使用路徑範圍的路由之間的名稱衝突。例如
scope 'admin' do
resources :photos, as: 'admin_photos'
end
resources :photos
這會將 /admin/photos
的路由輔助函式從 photos_path
、new_photos_path
等變更為 admin_photos_path
、new_admin_photo_path
等。如果在範圍化的 resources :photos
上未新增 as: 'admin_photos
,則未範圍化的 resources :photos
將不會有任何路由輔助函式。
若要為一組路由輔助函式加上前置字,請將 :as
與 scope
搭配使用
scope 'admin', as: 'admin' do
resources :photos, :accounts
end
resources :photos, :accounts
與之前一樣,這會將 /admin
範圍化資源輔助函式變更為 admin_photos_path
和 admin_accounts_path
,並允許未範圍化的資源使用 photos_path
和 accounts_path
。
namespace
範圍會自動新增 :as
以及 :module
和 :path
前綴。
4.5.1 參數範圍
你可以使用命名參數為路由加上前綴
scope ':account_id', as: 'account', constraints: { account_id: /\d+/ } do
resources :articles
end
這會提供給你例如 /1/articles/9
的路徑,並允許你參考路徑的 account_id
部分,例如在控制器、輔助函式和檢視中為 params[:account_id]
。
它也會產生加上 account_
前綴的路徑和 URL 輔助函式,你可以照預期將物件傳遞進去
account_article_path(@account, @article) # => /1/article/9
url_for([@account, @article]) # => /1/article/9
form_with(model: [@account, @article]) # => <form action="/1/article/9" ...>
我們使用 約束 將範圍限制為僅符合類似 ID 的字串。你可以根據你的需求變更約束,或完全省略它。:as
選項也不是絕對必要的,但如果沒有它,Rails 會在評估 url_for([@account, @article])
或其他依賴 url_for
的輔助函式(例如 form_with
)時產生錯誤。
4.6 限制建立的路由
預設情況下,Rails 會為應用程式中的每個 RESTful 路由建立七個預設動作(index
、show
、new
、create
、edit
、update
和 destroy
)的路由。你可以使用 :only
和 :except
選項微調此行為。:only
選項告訴 Rails 僅建立指定的路由
resources :photos, only: [:index, :show]
現在,對 /photos
的 GET
要求會成功,但對 /photos
的 POST
要求(通常會路由到 create
動作)會失敗。
:except
選項指定 Rails 不應建立的路由或路由清單
resources :photos, except: :destroy
在這種情況下,Rails 會建立所有正常的路由,除了 destroy
的路由(對 /photos/:id
的 DELETE
要求)。
如果你的應用程式有許多 RESTful 路由,使用 :only
和 :except
僅產生你實際需要的路由,可以減少記憶體使用量並加快路由程序。
4.7 已翻譯路徑
使用 scope
,我們可以變更由 resources
產生的路徑名稱
scope(path_names: { new: 'neu', edit: 'bearbeiten' }) do
resources :categories, path: 'kategorien'
end
Rails 現在會建立至 CategoriesController
的路由。
HTTP 動詞 | 路徑 | 控制器#動作 | 命名路由輔助函式 |
---|---|---|---|
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 取代單數形式
如果您想要取代資源的單數形式,您應該透過 inflections
新增額外的規則至 inflector
ActiveSupport::Inflector.inflections do |inflect|
inflect.irregular 'tooth', 'teeth'
end
4.9 在巢狀資源中使用 :as
:as
選項會取代巢狀路由輔助程式中自動產生的資源名稱。例如
resources :magazines do
resources :ads, as: 'periodical_ads'
end
這將會建立路由輔助程式,例如 magazine_periodical_ads_url
和 edit_magazine_periodical_ad_path
。
4.10 取代已命名路由參數
:param
選項會取代預設資源識別碼 :id
(用於產生路由的 動態區段 名稱)。您可以使用 params[<:param>]
從您的控制器存取該區段。
resources :videos, param: :identifier
videos GET /videos(.:format) videos#index
POST /videos(.:format) videos#create
new_video GET /videos/new(.:format) videos#new
edit_video GET /videos/:identifier/edit(.:format) videos#edit
Video.find_by(identifier: params[:identifier])
您可以取代關聯模型的 ActiveRecord::Base#to_param
來建構 URL
class Video < ApplicationRecord
def to_param
identifier
end
end
video = Video.find_by(identifier: "Roman-Holiday")
edit_video_path(video) # => "/videos/Roman-Holiday/edit"
5 將非常大的路由檔案拆分為多個小檔案
如果您在一個擁有數千個路由的大型應用程式中工作,單一的 config/routes.rb
檔案可能會變得繁瑣且難以閱讀。
Rails 提供一種方法,可以使用 draw
巨集將一個巨大的單一 routes.rb
檔案拆分為多個小檔案。
您可以有一個包含所有管理員區域路由的 admin.rb
路由,另一個 api.rb
檔案用於與 API 相關的資源,等等。
# config/routes.rb
Rails.application.routes.draw do
get 'foo', to: 'foo#bar'
draw(:admin) # Will load another route file located in `config/routes/admin.rb`
end
# config/routes/admin.rb
namespace :admin do
resources :comments
end
在 Rails.application.routes.draw
區塊內呼叫 draw(:admin)
本身會嘗試載入一個路由檔案,該檔案的名稱與給定的引數相同(此範例中為 admin.rb
)。該檔案需要位於 config/routes
目錄或任何子目錄中(即 config/routes/admin.rb
或 config/routes/external/admin.rb
)。
您可以在 admin.rb
路由檔案中使用一般的路由 DSL,但您不應該像在主 config/routes.rb
檔案中那樣,將其置於 Rails.application.routes.draw
區塊中。
5.1 除非您真的需要,否則請勿使用此功能
擁有多個路由檔案會讓可發現性和可理解性更困難。對於大多數應用程式而言,即使是擁有數百個路由的應用程式,開發人員也比較容易擁有一個單一的路由檔案。Rails 路由 DSL 已經提供了一種使用 namespace
和 scope
以有組織的方式中斷路由的方法。
6 檢查和測試路由
Rails 提供了檢查和測試路由的工具。
6.1 列出現有路由
若要取得應用程式中可用路由的完整清單,請在伺服器於開發環境中執行時,於瀏覽器中拜訪 https://127.0.0.1:3000/rails/info/routes。您也可以在終端機中執行 bin/rails routes
指令,以產生相同的輸出。
這兩種方法都會列出您的所有路由,順序與它們在 config/routes.rb
中出現的順序相同。對於每個路由,您會看到
- 路由名稱(如果有)
- 使用的 HTTP 動詞(如果路由未回應所有動詞)
- 要比對的 URL 模式
- 路由的路由參數
例如,以下是 RESTful 路由的 bin/rails 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
您也可以使用 --expanded
選項來開啟擴充表格格式化模式。
$ bin/rails routes --expanded
--[ Route 1 ]----------------------------------------------------
Prefix | users
Verb | GET
URI | /users(.:format)
Controller#Action | users#index
--[ Route 2 ]----------------------------------------------------
Prefix |
Verb | POST
URI | /users(.:format)
Controller#Action | users#create
--[ Route 3 ]----------------------------------------------------
Prefix | new_user
Verb | GET
URI | /users/new(.:format)
Controller#Action | users#new
--[ Route 4 ]----------------------------------------------------
Prefix | edit_user
Verb | GET
URI | /users/:id/edit(.:format)
Controller#Action | users#edit
您可以使用 grep 選項:-g,來搜尋您的路由。這會輸出任何與 URL 輔助方法名稱、HTTP 動詞或 URL 路徑部分比對的路由。
$ bin/rails routes -g new_comment
$ bin/rails routes -g POST
$ bin/rails routes -g admin
如果您只想看到對應到特定控制器的路由,可以使用 -c 選項。
$ bin/rails routes -c users
$ bin/rails routes -c admin/users
$ bin/rails routes -c Comments
$ bin/rails routes -c Articles::CommentsController
如果您將終端機視窗加寬,直到輸出行不再換行,您會發現從 bin/rails routes
輸出的內容更易於閱讀。
6.2 測試路由
路由應包含在您的測試策略中(就像您的應用程式其他部分一樣)。Rails 提供三種內建斷言,旨在簡化路由測試
6.2.1 assert_generates
斷言
assert_generates
斷言特定選項組會產生特定路徑,且可用於預設路由或自訂路由。例如
assert_generates '/photos/1', { controller: 'photos', action: 'show', id: '1' }
assert_generates '/about', controller: 'pages', action: 'about'
6.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 })
6.2.3 assert_routing
斷言
assert_routing
斷言會雙向檢查路由:它會測試路徑是否會產生選項,以及選項是否會產生路徑。因此,它結合了 assert_generates
和 assert_recognizes
的功能
assert_routing({ path: 'photos', method: :post }, { controller: 'photos', action: 'create' })
回饋
我們鼓勵您協助提升本指南的品質。
如果您看到任何錯字或事實錯誤,請協助我們修正。要開始,您可以閱讀我們的 文件貢獻 部分。
您也可能會發現內容不完整或過時。請務必新增 main 遺漏的任何文件。請務必先查看 Edge Guides,以驗證問題是否已在主分支中修正。查看 Ruby on Rails Guides Guidelines 以了解風格和慣例。
如果您發現需要修正的地方,但無法自行修補,請 開啟問題。
最後但並非最不重要的一點是,歡迎在 官方 Ruby on Rails 論壇 上討論與 Ruby on Rails 文件相關的任何事項。