理解路由的目的javascript
看懂routes.rb文件中的代碼html
使用經典的hash風格或者如今比較流行的Restful風格構造你本身的路徑java
判定一個路徑會映射到哪個controller和actiongit
路由的雙重做用github
Rails的路由是一個雙重的機制 - 你既能把樹木變成紙張,也能把紙張變成樹木.更準確的說,它既能將進入服務器的HTTP請求鏈接到你的controller,也能幫助你(在View內)生成URL而不須要使用硬編碼的字符串.web
從URL到代碼
當你的Rails應用接收到HTTP請求後,好比:數據庫
GET /patients/17安全
Rails的路由引擎就是把請求分發到你的應用中合適點的那些代碼.具體到這個例子,應用程序比較可能會運行patients controller中的show action,並展現id是17的patient的詳細信息.服務器
從代碼到URL
路由也能夠反過來做用.若是你的應用包含了如下代碼session
@patient = Patient.find(17)
<%= link_to 「Patient Record」, patient_path(@patient) %>
那麼,路由引擎就會把這段代碼解釋成這樣的URL: http://example.com/patients/17.這樣使用路由,比起硬編碼URL,能夠下降你的應用程序的脆弱程度(增長程序健壯性),而且能夠增長代碼的可讀性,使你的程序更容易被理解.
Patient須要被聲明成爲resource纔可使用這種形式的路徑轉換
快速瀏覽Routes.rb
Rails的路由有兩個組陳部分:做爲Rails一部分的路由引擎,還有包含有你的應用程序真正路徑的config/routes.rb文件.講授能夠把什麼東西放到routes.rb裏面去是本文的目的,不過在此以前咱們先概覽一下.
處理文件
從格式上講,routes.rb只不過是被傳遞給ActionController::Routing:routes.draw的一個大大的 block. 這個文件裏也能夠有註釋,不過大多數狀況下,應該是移行移行的代碼,每一行表明你應用程序的一條路徑.路徑有五種主要類型:
RESTful路徑 命名路徑 嵌套路徑 常規路徑 默認路徑
每個類型的路徑都會在隨後詳細闡述. 當有請求進入時, routes.rb文件自頂向下出路.請求會被分發到第一個匹配的路徑.若是沒有匹配的路徑,Rails會返回給調用者一個HTTP 404.
RESTful路徑
RESTful路徑沾了Rails內建REST機制的光,只用一句單獨的聲明就包含了不少路由信息.一個RESTful路徑看起來是這樣的:
map.resources :books
命名路徑
命名路徑在處理請求的同時,也能夠給你的代碼提供很是具備可讀性的連接.這是一個典型的命名路徑:
map.login ‘/login’, :controller => ’sessions’, :action => ‘new’
嵌套路徑
嵌套路徑可讓你在一個resource裏面包含另外一個resource.你待會兒就會知道它是怎麼被轉化成URL(絕對路徑 http://niuwa.org/index.html)和path(相對路徑 /index.html)的.舉例來講,若是你的應用包含不少部分,每一個部分屬於一個包(assembly),你也許能夠這麼寫:
map.resources :assemblies do |assemblies| assemblies.resources :parts end
常規路徑
在不少應用中,你會看到非RESTful的路由,它們顯式的連接URL和action.例如:
map.connect ‘parts/:number’, :controller => ‘inventory’, :action => ’show’
默認路徑
默認路徑十一個安全網,抓住那些沒有匹配上其餘路徑的請求.不少Rails應用都會包含這兩條默認路徑:
map.connect ‘:controller/:action/:id’ map.connect ‘:controller/:action/:id.:format’
這兩個默認路徑是你建立Rails應用的時候自動生成的.你能夠爲你的應用裏面的全部東西使用RESTful的路徑,那你就有可能想去掉這兩條默認路徑.在你去掉它們以前請確保你沒有用到它們.
`
RESTful路由:Rails的默認方式
RESTful路由是目前Rails的標準路由方式,而且它也是你在新建的應用中首先使用的方式.要理解RESTful路有可能要花上一點時間,不 過這樣 的努力是值得的;你的代碼會變得更容易閱讀,而且當你使用這種路由方式的時候,你會跟Rails配合的很好,而不是跟Rails對着幹.
什麼是REST?
RESTful 路由的基礎是由Roy Fielding的博士論文提出的,參見: Architectural Styles and the Design of Network-based Software Architectures. 幸運的是,你不須要看懂整篇博士論文就能夠理解Rails中的REST是怎麼回事.REST是Representational State Transfer(基於表現的狀態轉移)的縮寫.對咱們來講,主要有兩點:
使用資源定位符(對咱們來講就是URL)來表示資源 在系統的不一樣組件之間轉移表現狀態
舉例來講,對於Rails應用中這樣的一個請求:
DELETE /phote/17
會被理解成是操做ID是17的photo,而且指示出了指望的操做 - 刪除該資源.REST是web應用架構的一種天然的方式,而且Rails進一步使用約定(convention)隱藏了RESTful的某些複雜性使之更加天然(大概是指用起來更簡單).
CRUD,動詞和action
在RAils中,一個RESTful路徑提供了HTTP動詞(也就是GET, PUT等等),controller actions和(隱含的)CRUD數據庫操做之間的的映射.路由文件中一個單獨的入口以下所示:
map.resources :photos
在你的應用中建立了七個不一樣的路徑:
|
|
|
|
|
GET |
/photos |
Photos |
index |
display a list of all photos |
GET |
/photos/new |
Photos |
new |
return anHTMLform for creating a new photo |
POST |
/photos |
Photos |
create |
create a new photo |
GET |
/photos/1 |
Photos |
show |
display a specific photo |
GET |
/photos/1/edit |
Photos |
edit |
return anHTMLform for editing a photo |
PUT |
/photos/1 |
Photos |
update |
update a specific photo |
DELETE |
/photos/1 |
Photos |
destroy |
delete a specific photo |
對這些路徑(由以前的resource定義生成的),resource的id能夠在相應的controller action中使用params[:id]獲得.
若是你一直在你的應用程序裏面使用RESTful路徑,你應該刪除routes.rb中的默認路徑,這樣就會迫使Rails使用HTTP動詞和路徑間的映射.
URL和Path
建立RESTful路徑會在你的應用程序裏面生成一堆helper:
photos_url 和photos_path映射到index和create兩個action new_photo_url和new_photo_path映射到new action edit_photo_url和edit_photo_path映射到edit action photo_url和photo_path映射到show, update和destroy三個action
由於路由同時使用HTTP動詞和path兩者分發請求,所以這七個RESTful路由生成的路徑只有4對helper.
在這裏,以_url結尾的helper生成應用程序能理解的整個URL(包含主機名),而以_path結尾的helper僅生成從應用程序根目錄開始的path(不包含主機名).例如:
photos_url# => 「http://www.example.com/photos」
photos_path# => 「/photos」
同時定義多個資源
若是你須要爲多個資源建立路徑,你可使用一次map.resources調用就定義它們:
map.resources :photos, :books, :videos
跟這樣定義的效果是同樣的:
map.resources :photos map.resources :books map.resources :videos
單數形式資源
你也能夠應用RESTful路由定義單數形式的資源.這是,你須要使用map.resource代替剛纔的map.resources,這時生成的路徑會略有不一樣:
map.resource :geocoder
建立六個不一樣的路徑:
|
|
|
|
|
GET |
/geocoder/new |
Geocoders |
new |
return anHTMLform for creating the new geocoder |
POST |
/geocoder |
Geocoders |
create |
create the new geocoder |
GET |
/geocoder |
Geocoders |
show |
display the one and only geocoder resource |
GET |
/geocoder/edit |
Geocoders |
edit |
return anHTMLform for editing the geocoder |
PUT |
/geocoder |
Geocoders |
update |
update the one and only geocoder resource |
DELETE |
/geocoder |
Geocoders |
destroy |
delete the geocoder resource |
雖然routes.rb中的資源名稱是單數,可是對應的controller名稱依然是複數.
一個單數形式的RESTful路徑生成以下的helper:
new_geocoder_url 和new_geocoder_path映射到new action edit_geocoder_url和edit_geocoder_path映射到edit action geocoder_url和geocode_path映射到create, update和destroy三個action 自定義資源
雖然使用RESTful路由約定對不少應用來講基本就足夠了,不過仍是有一些其餘的方法自定義RESTful路徑的工做方式.這些選項包括:
:controller :singular :requirements :conditions :as :path_names :path_prefix :name_prefix :only :except
你也可使用:member和:collection選項添加路徑,這個在本文中稍後討論.
使用:controller
:controller選項使你可使用和公開的資源名稱不一樣的controller名稱,例如:
map.resources :photos, :controller => 「images」
生成helper時會依據資源的名稱,
而不是controller的名稱.所以,在這個例子裏,就會獲得photos_path,new_photo_path等等.
controller命名空間和路由
默認路由:
# Rails3:
match '/:controller(/:action(/:id))'
# Rails2:
map.connect ':controller/:action/:id'
# Rails3: match '/:controller(/:action(/:id))' # Rails2: map.connect ':controller/:action/:id'
正則路由:
# Rails3:
match 'products/:id', :to => 'catalog#view'
# Rails2:
map.connect 'products/:id', :controller => 'catalog', :action => 'view'
# Rails3: match 'products/:id', :to => 'catalog#view' # Rails2: map.connect 'products/:id', :controller => 'catalog', :action => 'view'
命名路由:
# Rails3:
match 'logout', :to => 'sessions#destroy', :as => 'logout'
# Rails2:
map.logout 'logout', :controller => 'sessions', :action => ''
# Rails3: match 'logout', :to => 'sessions#destroy', :as => 'logout' # Rails2: map.logout 'logout', :controller => 'sessions', :action => ''
根路由:
# Rails3:
root => 'welcome#show'
# Rails2:
map.root :controller => 'welcome', :action => 'show'
# Rails3: root => 'welcome#show' # Rails2: map.root :controller => 'welcome', :action => 'show'
路由簡寫技巧:
:to 鍵的省略:
match 'account' => 'account#index'
# 至關於:
match 'account', :to => 'account#index'
match 'info' => 'projects#info', :as => 'info'
match 'account' => 'account#index' # 至關於: match 'account', :to => 'account#index' match 'info' => 'projects#info', :as => 'info'
注意:
:as 在rails3中是改變 helper, 在rails2中是改變 path
當路徑和控制器(及action)一至時,可省略指派控制器部分
match 'account/overview'
# 至關於:
match 'account/overview', :to => 'account#overview'
match 'account/overview' # 至關於: match 'account/overview', :to => 'account#overview'
Verb路由
當須要限制http請求方法的時候經過鍵
:via ,也能夠直接把方法寫在最前面:
get 'account/overview'
# 至關於:
match 'account/overview', :via => 'get'
match 'account/setup', :via => [:get, :post]
# 支持get\post\put\delete四種HTTP方法
get 'account/overview' # 至關於: match 'account/overview', :via => 'get' match 'account/setup', :via => [:get, :post] # 支持get\post\put\delete四種HTTP方法
resources路由:
resources :posts, :except => [:index]
resources :posts, :only => [:new, :create]
# edit_post GET /posts/:id/modify(.:format) {:controller=>"posts", :action=>"edit"}
resources :posts, :path_names => { :edit => 'modify' }
resources :projects do
resources :tasks, :people
end
resources :products do
collection do
get :sold
post :on_offer, :search
end
get :buy, :on => :member
post :batch, :on => :collection
end
resource :session do
get :create
end
resources :posts, :except => [:index] resources :posts, :only => [:new, :create] # edit_post GET /posts/:id/modify(.:format) {:controller=>"posts", :action=>"edit"} resources :posts, :path_names => { :edit => 'modify' } resources :projects do resources :tasks, :people end resources :products do collection do get :sold post :on_offer, :search end get :buy, :on => :member post :batch, :on => :collection end resource :session do get :create end
:shallow用法:
Rails3中的shallow用法與Rails2中一致
resources :blogs, :shallow => true do
resources :comments
end
resources :blogs, :shallow => true do resources :comments end
使用:shallow先後相同部分:
blog_comments |
GET |
/blogs/:blog_id/comments(.:format) |
{:controller=>"comments", :action=>"index"} |
blog_comments |
POST |
/blogs/:blog_id/comments(.:format) |
{:controller=>"comments", :action=>"create"} |
new_blog_comment |
GET |
/blogs/:blog_id/comments/new(.:format) |
{:controller=>"comments", :action=>"new"} |
blogs |
GET |
/blogs(.:format) |
{:controller=>"blogs", :action=>"index"} |
blogs |
POST |
/blogs(.:format) |
{:controller=>"blogs", :action=>"create"} |
new_blog |
GET |
/blogs/new(.:format) |
{:controller=>"blogs", :action=>"new"} |
edit_blog |
GET |
/blogs/:id/edit(.:format) |
{:controller=>"blogs", :action=>"edit"} |
blog |
GET |
/blogs/:id(.:format) |
{:controller=>"blogs", :action=>"show"} |
blog |
PUT |
/blogs/:id(.:format) |
{:controller=>"blogs", :action=>"update"} |
blog |
DELETE |
/blogs/:id(.:format) |
{:controller=>"blogs", :action=>"destroy"} |
|
使用:shallow先後不一樣部分:
不使用shallow選項:
edit_blog_comment |
GET |
/blogs/:blog_id/comments/:id/edit(.:format) |
{:controller=>"comments", :action=>"edit"} |
blog_comment |
GET |
/blogs/:blog_id/comments/:id(.:format) |
{:controller=>"comments", :action=>"show"} |
blog_comment |
PUT |
/blogs/:blog_id/comments/:id(.:format) |
{:controller=>"comments", :action=>"update"} |
blog_comment |
DELETE |
/blogs/:blog_id/comments/:id(.:format) |
{:controller=>"comments", :action=>"destroy"} |
使用shallow選項後:
edit_comment |
GET |
/comments/:id/edit(.:format) |
{:controller=>"comments", :action=>"edit"} |
comment |
GET |
/comments/:id(.:format) |
{:controller=>"comments", :action=>"show"} |
comment |
PUT |
/comments/:id(.:format) |
{:controller=>"comments", :action=>"update"} |
comment |
DELETE |
/comments/:id(.:format) |
{:controller=>"comments", :action=>"destroy"} |
能夠看出使用shallow選項後,對於已經存在的資源使用簡化方式操做,具體行爲涉及到 edit\show\update\destroy 四種
另外,shallow選項的有效範圍是對自身及嵌套的資源都有效,以下面這個例子:
resources :publishers do
resources :magazines do
resources :albums, :shallow => true do
resources :photos do
resources :images
end
end
end
end
resources :publishers do resources :magazines do resources :albums, :shallow => true do resources :photos do resources :images end end end end
這個例子中 albums、photos、images 都會使用簡化方式,而 magazines 不會。特別注意:這種嵌套方式極不推薦,通常嵌套的層級最好不要超過一級
scope路由
:path 改變Path,:module 改變Controller, :name_prefix || :as 改變 helper
scope 'admin' do resources :posts end # 行當於: scope :path => 'admin' do resources :posts end
scope 'admin' do resources :posts end # 行當於: scope :path => 'admin' do resources :posts end
Ruby代碼
scope :module => 'admin' do
resources :posts
end
# 至關於:
resources :posts, :module => 'admin'
scope :module => 'admin' do resources :posts end # 至關於: resources :posts, :module => 'admin'
scope :name_prefix => 'admin' do
resources :posts
end
# 至關於:
resources :posts, :name_prefix => 'admin'
scope :name_prefix => 'admin' do resources :posts end # 至關於: resources :posts, :name_prefix => 'admin'
scope 'admin', :module => 'admin', :name_prefix => 'admin' do
resources :posts
end
# 至關於:
namespace 'admin' do
resources :posts
end
scope 'admin', :module => 'admin', :name_prefix => 'admin' do resources :posts end # 至關於: namespace 'admin' do resources :posts end
在路由中定義跳轉:
match "/posts/github" => redirect("http://github.com/rails.atom")
# 地址 /foo/1 會自動跳轉到 /bar/1s
match "/foo/:id", :to => redirect("/bar/%{id}s")
# /account/proc/inosin 會自動跳轉到 /inosins
match 'account/proc/:name', :to => redirect {|params|
"/#{params[:name].pluralize}" }
match "/stories" => redirect {|p, req| "/posts/#{req.subdomain}" }
match "/posts/github" => redirect("http://github.com/rails.atom") # 地址 /foo/1 會自動跳轉到 /bar/1s match "/foo/:id", :to => redirect("/bar/%{id}s") # /account/proc/inosin 會自動跳轉到 /inosins match 'account/proc/:name', :to => redirect {|params| "/#{params[:name].pluralize}" } match "/stories" => redirect {|p, req| "/posts/#{req.subdomain}" }
路由中的限制:
# 限制 id 只能爲數字
match "/posts/show/:id", :to => "posts#index", :id => /\d+/
match "/posts/show/:id", :to => "posts#index", :constraints => {:id => /\d+/}
# 限制子域名
match "photos", :constraints => {:subdomain => "admin"}
# 限制訪問者 IP
constraints(:ip => /127.0.0.1/) do
match '/questions', :to => redirect("http://www.stackoverflow.com/")
end
# 當訪問者 ip 是 192.168.1.* 的來訪者訪問 子域名爲 "test"
match "/ttt" => proc{|env| [200, {}, ["hello test"]]}, \
:constraints => {:subdomain => "test", :ip => /192\.168\.1\.\d+/}
# 限制 id 只能爲數字 match "/posts/show/:id", :to => "posts#index", :id => /\d+/ match "/posts/show/:id", :to => "posts#index", :constraints => {:id => /\d+/} # 限制子域名 match "photos", :constraints => {:subdomain => "admin"} # 限制訪問者 IP constraints(:ip => /127.0.0.1/) do match '/questions', :to => redirect("http://www.stackoverflow.com/") end # 當訪問者 ip 是 192.168.1.* 的來訪者訪問 子域名爲 "test" match "/ttt" => proc{|env| [200, {}, ["hello test"]]}, \ :constraints => {:subdomain => "test", :ip => /192\.168\.1\.\d+/}
路由通配符:
resources :photos, :id => /\d+/
match 'photos/*other' => 'photos#unknown'
#上面這兩行路由則會把不符合7種path的其餘url所有解析到PhotoController#unknown中去處理,params[:other]可獲得path中/photos/以後的部分,注意這兩行的順序不能顛倒
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"
resources :photos, :id => /\d+/ match 'photos/*other' => 'photos#unknown' #上面這兩行路由則會把不符合7種path的其餘url所有解析到PhotoController#unknown中去處理,params[:other]可獲得path中/photos/以後的部分,注意這兩行的順序不能顛倒 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"
Rack:
match "/foo", :to => proc {|env| [200, {}, ["Hello world"]] }
match 'rocketeer.js' => ::TestRoutingMapper::RocketeerApp
RocketeerApp = lambda { |env|
[200, {"Content-Type" => "text/html"}, ["javascripts"]]
}
match "/foo", :to => proc {|env| [200, {}, ["Hello world"]] } match 'rocketeer.js' => ::TestRoutingMapper::RocketeerApp RocketeerApp = lambda { |env| [200, {"Content-Type" => "text/html"}, ["javascripts"]] }