文/零小白(簡書作者)
原文鏈接:http://www.jianshu.com/p/Ro4HZT
參考 RailsGuides中的Rails Routing from the Outside In
簡介
Rails 路由會通過你配置的路由規則將發送來的 URL 分發到對應的 action 中。它同時會生成 paths 和 urls 來避免你在視圖中使用硬編碼。
Rails 的路由器是通過 HTTP 動詞和 url 地址來匹配路由的。Rails 支持四種 HTTP 動詞,包括get,put,post,delete。其中 post 和 delete 瀏覽器本身不支持,是通過 js 來實現的。Rails 路由器生成一個 params 的散列表其中的 :controller 和 :action 就決定了相應的控制器和相應的動作來處理該請求,其餘的通常作爲參數傳遞給該動作,該動作使用。例如:
match ':controller/:action/:id/with_user/:user_id'
生成的 params 是:
{ :controller => “photos”, :action => “show”, :id => “1”, :user_id => “2” }
Rails 匹配地址時是在你指定的所有路由地址中從上倒下匹配的。如果存在如下代碼:
resources :pohots
get 'photos/poll'
當傳來 'photos/poll' 時會先匹配到 resources 路由的 show 動作上,而不會匹配到 get 那個地址。
其次,同時意味着你可以把 root 地址放在 rout 文件的開頭,因爲這是很可能匹配最多的地址。
資源路由
基本使用
Rails 資源路由有兩種,resources 和 resource。其中後者針對單件資源的路由,前者更加常見。
其中使用方法如下:
resources :photos
resource :photo
分別生成七個路由地址和六個路由地址。(很簡單,不解釋)
對動作進行限制
默認地, Rails 會在應用中爲每一個 RESTful 路由建立7個動作。不過你可以用 :only 或 :except 選項來針對你的路由進行調整:
resources :photos, :only => [:index, :show]
resources :photos, :except => :destroy
如果你的應用程序有很多 RESTful 路由,使用 :only 和 :except 可以只生成有用的路徑,這能夠減少匹配所需時間和內存。
添加額外的動作
成員路由
加入一個成員路由只需要在你的 resource 代碼塊中加入你一個 member 代碼塊就好:
resources :photos do
member do
get 'preview'
end
end
這樣將會把 /photos/1/preview 的 GET 動作識別出來,然後路由會匹配到 PhotosController 控制器下面的 preview 行爲中。它同樣會建立兩個Helper:preview_photo_url 和 preview_photo_path。
當你使用了路由中的 Member 代碼塊時,你可以任意指定一個HTTP動詞: get, patch/put, post, 或者 delete 。如果你並沒有太多的 Member 路由規則,可以用 :on 作爲後綴來把代碼塊替換掉:
resources :photos do
get 'preview', :on => :member
end
集合路由
加入集合路由和加入成員路由的格式完全一樣,只不過是用 collection 的方法:
resources :photos do
collection do
get 'search'
end
end
上面這段代碼將會讓 Rails 用 GET 方法匹配路徑 /photos/search。並且這個 search 行爲將會匹配到 PhotosController下,它將會創建 search_photos_url 和 search_photos_path 兩個路由Helper。
同成員路由一樣,你可以傳入一個 :on 選項。
resources :photos do
get 'search', :on => :collection
end
指定控制器
:controller 選項能夠讓你制定使用的控制器。例如:
resources :photos, :controller => "images"
它沒有改變匹配的 URL 和生成的 helper,只是將控制器指定爲 images。
指定格式限制
:constraints 選項可以讓你限定 id 的格式。例如:
resources :photos, :constraints => {:id => /[A-Z][A-Z][0-9]+/}
你可以用一個代碼塊來把單個正則限制運用到多條規則上去。
constraints(:id => /[A-Z][A-Z][0-9]+/) do
resources :photos
resources :accounts
end
改變 Helper
:as 選項能讓你把默認命名的路由 Helper 重命名成定製的字符串。例如:
resources :photos, :as => "images"
這樣就會生成像 new_image_path 這樣的 Helper。
改變匹配路徑動的動作
:path_name 選項能夠讓你把生成路徑中的 "new" 和 "edit" 覆蓋:
resources :photos, :path_names => { :new => 'make', :edit => 'change' }
這將會讓 Rails 能夠匹配這些請求:
/photos/make
/photos/1/change
這個選項並不會改變控制器中的行爲名稱,這兩個路徑依然會被匹配到控制器中的 new 和 edit 路徑中去。
如果你想一次性地把一條設置應用到多個規則上,你可以用 scope 。
scope :path_names => { :new => "make" } do
resources :photos
resources :accounts
end
改變匹配的路徑的資源名
:path 選項能夠讓你把生成路徑中的資源名稱覆蓋:
resources :categories, :path => "kategorien"
resources :posts, :path => "/admin/posts"
這樣路由就會匹配這樣的路徑:/kategorien/:id/edit,而不再是:/categories/:id/edit,但是並不會改變 Helper 。
有時,你會把同類的控制器都放在同一個目錄下,例如: app/controllers/admin 。這時,你可以這樣組織路由:
scope :path => "/admin" do
resources :posts, :comments
end
通常寫爲:
scope "/admin" do
resources :posts, :comments
end
同樣,你還可以用下面的控制器模塊和命名空間這兩種方法。
HTTP Verb Path action name helper
GET admin/photos posts#index photos_path
GET admin/photos/new posts#new new_photos_path
POST admin/photos posts#create photos_path
GET admin/photos/:id posts#show photo_path(:id)
GET admin/photos/:id/edit posts#edit edit_photo_path(:id)
PUT admin/photos/:id posts#update photo_path(:id)
DELETE admin/photos/:id posts#destroy photo_path(:id)
控制器模塊
:module 可以讓你的控制器匹配到一個特定的模塊下(可能包含有多個控制器)。
resources :posts, :module => "admin"
這樣把路由 /posts (不以 /admin 作前綴) 匹配到 Admin::PostsController。
當然也可以組織多個路由:
scope :module => "admin" do
resources :posts, :comments
end
其中 posts 的路由地址如下:
HTTP Verb Path action name helper
GET /photos admin/posts#index photos_path
GET /photos/new admin/posts#new new_photos_path
POST /photos admin/posts#create photos_path
GET /photos/:id admin/posts#show photo_path(:id)
GET /photos/:id/edit admin/posts#edit edit_photo_path(:id)
PUT /photos/:id admin/posts#update photo_path(:id)
DELETE /photos/:id admin/posts#destroy photo_path(:id)
控制器命名空間
namespace 將會自動爲你添加 :as , :module 和 :path 前綴。特別的用來處理下面提到的這種情況:
控制器命名空間可以幫助你通過一個命名空間管理多個控制器。例如,有多個 controllers 在同一個命名空間 Admin::namespace 下。 你或許是將這些 controllers 文件都放在了 app/controllers/admin 的目錄之下。這時,你可以這樣組織你的路由:
namespace :admin do
resources :photos, :comments
end
其中 photos 的路由地址如下:
HTTP Verb Path action name helper
GET /admin/photos admin/posts#index admin_photos_path
GET /admin/photos/new admin/posts#new new_admin_photos_path
POST /admin/photos admin/posts#create admin_photos_path
GET /admin/photos/:id admin/posts#show admin_photo_path(:id)
GET /admin/photos/:id/edit admin/posts#edit edit_admin_photo_path(:id)
PUT /admin/photos/:id admin/posts#update admin_photo_path(:id)
DELETE /admin/photos/:id admin/posts#destroy admin_photo_path(:id)
嵌套路由
通常,一個 Resource 常常有數個邏輯上的子 Resource 。例如,你的應用有這樣一個模型:
class Magazine < ActiveRecord::Base
has_many :ads
end
class Ad < ActiveRecord::Base
belongs_to :magazine
end
嵌套路由允許你這樣嵌套路由:
resources :magazines do
resources :ads
end
在這裏,對於每一個 magazines 的路徑下面都需要能夠有 ad 作爲 Resource 匹配到AdsController。 這時候每一組 ad 都需要指定一個 magzine 作爲前綴。
HTTP Verb Path action name helper
GET /magazines/:magazine_id/ads index magazine_ads_path
GET /magazines/:magazine_id/ads/new new new_magazine_ads_path
POST /magazines/:magazine_id/ads create magazine_ads_path
GET /magazines/:magazine_id/ads/:id show magazine_ad_path(:id)
GET /magazines/:magazine_id/ads/:id/edit edit edit_magazine_ad_path(:id)
PUT /magazines/:magazine_id/ads/:id update magazine_ad_path(:id)
DELETE /magazines/:magazine_id/ads/:id destory magazine_ad_path(:id)
嵌套路由允許你進行多層的嵌套,但實際上你的嵌套永遠不要超過一層。
對象和路徑URL
在 Rails 中你可以把對象的實例傳入作參數來作爲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 這個 Helper。而如果你用的是 link_to 這樣的Helper,你可以和調用 url_for 一樣用對象作參數:
<%= link_to "Ad details", [@magazine, @ad] %>
如果你並沒有用嵌套式的路由,只要這樣就可以訪問一個對象的 show 方法:
<%= link_to "Magazine details", @magazine %>
如果你需要指定動作的對象的話,你也可以這樣:
<%= link_to "Edit Ad", [:edit, @magazine, @ad] %>
非資源路由
雖然 Resourceful 的路由規則功能強大,但是在很多時候使用一個簡單路由規則更爲合適。如果你覺得簡單路由規則更合適,你並沒有必要把應用中的每個規則都往 Resourceful 框架上套。
默認路由
match 爲 Rails 的默認路由,你需要提供一組符號字面量來讓 Rails 匹配收到的 HTTP 請求,其中有兩個符號量尤其的特別::controller 位置會匹配到你應用中名字對應的控制器,:action 有匹配到你應用中對應的控制器中的行爲。例如:
match ':controller(/:action(/:id))'
如果收到的請求是 /photos/show/1 (前提是它還沒有被其他的前面的路由規則匹配),那麼按照這個規則 Rails 將會調用 PhotosController 控制器中的 show 行爲,然後把後面的參數 "1" 放到 params[:id] 中以供使用。因爲 :action 和 :id 被圓括號包圍,所以,他倆可以被忽略,所以這條規則同樣能夠將 /photos 匹配到 PhotosController#index 。
動態部分
你可以設置一條路由規則有任意多的動態部分,除了:controller 或 :action ,其他的片段都將會被轉換成 params 的一部分。如果你寫了這樣的一條路由:
match ':controller/:action/:id/:user_id'
如果收到了 /photos/show/1/2 這樣的請求,將會分發到 PhotosController 下的 show 行爲中,而 params[:id] 會是 "1", params[:user_id] 是 "2".
靜態部分
你可以在創建路由規則的時候指定一個靜態的部分:
match ':controller/:action/:id/with_user/:user_id'
這個路由將會生成像這樣的路徑 /photos/show/1/with_user/2。在這個例子裏, params 是 { :controller => “photos”, :action => “show”, :id => “1”, :user_id => “2” }.
攜帶附加參數
你也可以在一個 urls 中攜帶任何的參數,例如:
match ':controller/:action/:id'
如果接收到的路徑爲 /photos/show/1?user_id=2 ,那麼 Rails 會把它分發到 Photos 控制器下面的 show 行爲中去,params 會是 { :controller => “photos”, :action => “show”, :id => “1”, :user_id => “2” }.
設置默認值
如果你需要一個默認的匹配值,你可以用一個沒有指定 :controller 和 :action 的路由規則來實現。像這樣:
match 'photos/:id' => 'photos#show'
Rails 將會因爲這條規則而把接收到的路徑 /photos/12 ,匹配到 PhotosController 下面的 show 裏面去。
你還可以通過提供一個 :defaults 的散列來指定一個你默認存在的動態片段。例如:
match 'photos/:id' => 'photos#show', :defaults => { :format => 'jpg' }
這樣 photos/12 的路徑就會匹配到 PhotosController 的 show 行爲, params[:format] 會被設置爲 "jpg"。
命名路由
你可以用 :as 參數來定義一個路由的名字。
match 'exit' => 'sessions#destroy', :as => :logout
這樣在應用程序中產生的 Helper 就將會是 create logout_path 和 logout_url。而調用了 logout_path 就會返回 /exit 路徑。
HTTP 動詞約定
你可以用 :via 選項來限定一個請求能相應的一個或者多個 HTTP 方法:
match 'photos/show' => 'photos#show', :via => :get
簡寫作:
get 'photos/show'
你也可以將多個動詞行爲綁定到一條路由規則上去:
match 'photos/show' => 'photos#show', :via => [:get, :post]
部分正則約定
你可以使用 :constraints 選項來對動態路徑部分強制性的匹配:
match 'photos/:id' => 'photos#show', :constraints => { :id => /[A-Z]\d{5}/ }
這個路由將會匹配像 /photos/A12345 這樣的路由。你可以用如此更簡潔的方式來寫出這個規則:
match 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
在 :constraints 選項中使用正則表達式有一個限制,就是在正則匹配中無法使用錨元素來匹配,像這樣的路由規則是無效的:
match '/:id' => 'posts#show', :constraints => {:id => /^\d/}
總之,確定你沒有在路由正則中使用錨元素。因爲每個路由規則在匹配的時候就已經用到了錨元素。
例如,下面的路由 posts 和 users 控制器共享了一個命名空間,他們根據 to_param 值的首字符是否是數字來區分,像 1-hello-world 這樣的會匹配到 posts 下面去,而像 david 就會匹配到 users 下面去。
match '/:id' => 'posts#show', :constraints => { :id => /\d.+/ }
match '/:username' => 'users#show'
請求限制 && 高級限定
這裏不是狠懂,設計到了 Request 對象,暫時留下以後解決。
通配路由匹配
通配路由規則能夠指定一個參數讓任意部分匹配。例如:
match 'photos/*other' => 'photos#unknown'
這樣這個路由將會匹配 photos/12 或 /photos/long/path/to/12,並且把 params[:other] 設置成 "12" 或 "long/path/to/12".
通配字符串可以放在路由規則的任意部分,例如:
match 'books/*section/:title' => 'books#show'
這樣將會把像 books/some/section/last-words-a-memoir 這樣的字符串匹配後並將 params[:section] 設置爲 "some/section", params[:title] 設置爲 "last-words-a-memoir"。
從技術上來說你是可以將超過一個統配的字符串加在你的路由規則中的,但是請記住,通配符總是會用貪婪地(儘可能多地匹配)將字符串匹配到路徑上。例如:
match '*a/foo/*b' => 'test#index'
將會把 zoo/woo/foo/bar/baz 的參數 params[:a] 設置成 "zoo/woo", 然後參數 params[:b] 設置成 "bar/baz"。
從 Rails 3.1 開始,通配路由總是會自動地將格式字符串默認匹配,例如下面這個路由:
match '*pages' => 'pages#show'
如果你發起了'/foo/bar.json'這樣的一個請求,你的參數 params[:pages] 將會是 "foo/bar" ,而你的請求格式會識別爲 JSON 。 如果你想要在舊的 3.0.x 中有同樣的特性,你需要提供一個散列參數:format => false,像這樣:
match '*pages' => 'pages#show', :format => false
如果你想要強制地被指定一種格式,(其無法被忽略),你可以像這樣加上 :format => true 參數:
match '*pages' => 'pages#show', :format => true
重定向
你可以在路由規則中通過 redirect 指定一個路徑重定向到另一個路由中的路徑:
match "/stories" => redirect("/posts")
你同樣可以把一條 match 命令中的動態部分的錯誤處理(rescue)進行重定向:
match "/stories/:name" => redirect("/posts/%{name}")
你同樣可以爲你的重定向函數加入一個代碼塊,代碼塊接收的變量是 params 和 請求對象(後者是可選的):
match "/stories/:name" => redirect {|params| "/posts/#{params[:name].pluralize}" }
match "/stories" => redirect {|p, req| "/posts/#{req.subdomain}" }
請注意,這裏的重定向都將是 301 標誌 “永久重定向”。在某些瀏覽器或者代理服務器中這個標識碼將會使舊的頁面失效。
路由定向到 Rack Applications
你可以將 urls 的地址交給任何一個 Rack application 來處理。只需要在 match 後面指定相應的 Rack application 來取代一個類似與 "posts#index" 的字符串。
match "/application.js" => Sprockets
這樣 Sprockets 將會處理這個請求並且返回 [status, headers, body], 但是這對於路由器來說是完全透明的,它不能分辨兩者。
值得一提的是,"post#index" 通常會自動的擴展爲 PostsController.action(:index),而它會返回一個可以有效的 Rack application。
跟目錄
你可以用 root 方法來指定 Rails 如何匹配 "/" 路由:
root :to => 'pages#main'
簡寫爲:
root 'pages#main'
你可以將 root 規則放在文件的最上方,這樣這條規則能第一個進行匹配,因爲這恐怕將會是最常用的的規則了。你同樣需要刪除文件 public/index.html 來讓這個規則發揮效用。
對路由進行檢查和測試
Rails 提供了檢查和測試路由的工具。
通過 rake 查看存在的路由規則
如果你想要一整套應用的完整可用的路由規則列表,運行 rake routes 命令就好。
路由測試
你的測試中應該加入路由測試(就和你應用程序的其他部分一樣)。Rails 提供三個 內建的斷言 被設計來幫助你進行路由的測試。
- assert_generates
- assert_recognizes
- assert_routing
assert_generates 能夠對是否生成指定的路徑進行斷言:
assert_generates "/photos/1", { :controller => "photos", :action => "show", :id => "1" }
assert_generates "/about", :controller => "pages", :action => "about"
你可以提供一個 :method 參數來指定使用的 HTTP 行爲:
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 })
assert_routing 對一個路由雙向地進行測試: 它測試的一條路徑是否與選項匹配,之後測試選項是否生成路徑。因此,這條斷言結合了前兩個測試方法 assert_generates 和 assert_recognizes.
assert_routing({ :path => "photos", :method => :post }, { :controller => "photos", :action => "create" })
其他
- 默認情況下 :id 參數不接收 點 ———— 這是因爲 點已經被用作了格式分割符。如果你需要在 :id 中的正則中匹配出 點,你需要這樣複寫正則表達式,例如::id => /[^\/]+/ ,這樣會允許除了斜槓之外的任何字符。