Rails路由

此文原文在http://guides.rubyonrails.org/routing.html。这是我根据自己的理解作的一些笔记。

1 使用路由有两上目的
1.1 连接URLs
当Rails应用收到HTTP的请求时,Rails会响应
GET /patients/17

Rails中的路由引擎是一个代码片断。在这个例子中,可能会运行patients控制器的show方法。显示ID为17的详细信息。

1.2 从代码生成URLs
路由也是可以反向工作的。假如应用中包含下面的代码:
@patient = Patient.find(17)

<%= link_to "Patient Record", patient_path(@patient) %>

这个路由引擎片断会转化成类似http://example.com/patients/17这样的URL。使用这样的方法可以减少代码中的硬编码,而且这样更容易阅读。

2 快览routes.rb
在Rails中路由有两个部分,一个是Rails提供的路由引擎本身,和文件config/routes.rb,文件包含了将被应用程序应用的实际路由。

2.1 处理文件
routes.rb文件主要关心的内容是ActionController:Routing::Routes.draw方法后的一个代码块。在应用程序中一般每一行开始一个路由,在这个文件中我们将会发现有5个主要的类型:
·RESTful Routes # 资源路由
·Named Routes    # 命名路由
·Nested Routes   # 嵌套路由
·Regular Routes # 正则路由
·Default Routes # 默认路由

当有请求时,routes.rb文件会从上至下进行匹配处理,如果没有匹配的,就返回404错误给浏览器。

2.2 RESTful Routes
RESTful路由的优势在于通过一个简单的声明,就能得到包含很多REST方式的路由信息。一个RESTful路由看起像这样:
map.resources :books

2.3 Named Routes
命名路由在给一个定义的路由取一个名字,这样让代码就更容易阅读。
map.login '/login', :controller => 'sessions', :action ='new'

2.4 Nested Routes
嵌套路由让我们声明一个资源包含另一个资源。比如,应用程序中包含一个parts,而每个parts又属于一个assembly,就可以这样声明嵌套路由。

map.resources :assemblies do |assemblies|
assemblies.resources :parts
end

2.5 Regular Routes
在更多的应用中,可能看到一些非RESTful的路由设置,它通过特定的action来联接。比如:
map.connect 'parts/:number', :controller => 'inventory', :action => 'show'

2.6 Default Routes
routes.rb会包含下面的代码:
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
这些默认的路由声明是在创建Rails应用时自动创建的。如果在你的应用程序中使用RESTful路由,那建议把他们移除。

3 Rails默认的RESTful路由
RESTful路由是当前Rails中的标准路由。你会喜欢上这个新应用的。在使用它之前,有必要花点时间来了解它是如何工作的,但这些时间是花得值得的。而且,使用这种风格的路由,让代码更容易阅读。

3.1 什么是REST?
REST的是Roy Fielding's 博士在2000年他的论文中出来的一种软件架构风格,REST全名Representational State Transfer,中文译为表象化状态迁移。它归结了两点主要原则:
·使用资源标识符来描述资源。
·系统组件之间的资源状态迁移。

例如:Rails的一个请求像这样:
DELETE /photos/17

这会归结为一个ID为17的photo资源,并要求使用的删除操作来处理这个资源。在WEB应用程序的架构中,REST是一种很自然的风格。Rails遵守一种约定,让处理复杂的RESTful更自然。


3.2 CRUD, Verbs and Actions
在Rails里,一个RESTful提供了一个HTTP vers, 控制器action和数据库CRUD操作之间的映射。一个简单的资源路由:

map.resources :photos

在你的应用程序中,这一行会创建7个不的路由。
HTTP verb    URL    controller     action     used for
GET        /photos        Photos     index         display a list of all photos
GET        /photos/new        Photos     new         return an HTML form 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 an HTML form for editing a photo
PUT        /photos/1        Photos     update         update a specific photo
DELETE     /photos/1        Photos     destroy     delete a specific photo

3.3 URLs和Paths
在应用程序内部,创建了一个RESTful路由就会生成一些有用的辅助方法:
·photos_url和photos_path映射了index和create方法的路径
·new_photo_url和new_photo_path映射了new方法的路径
·edit_photo_url和edit_photo_path映射了edit方法的路径
·photo_url和photo_path映射了show, update和destroy方法的路径
在这个例子中_url辅助方法会生成完整的URL的绝对路径,_path会生成相对路径。
例如:
photos_url # => "http://www.example.com/photos"
photos_path #=> "/photos"

3.4 在同一个时间定义多资源。
如果要创建多资源,可以调用map.resources方法:
map.resources :photos, :books, :videos
这一行也可以分开来写:
map.resources :photos
map.resources :books
map.resources :videos

3.5 单资源
在应用程序中也可以定义单资源路由。就要需要使用map.resource方法,而不是map.resources方法。例如:
map.resource :geocoder
这将生成6个不同的路由:

Http verb      URL          controller action           used for
GET         /geocoder/new   Geocoders     new     return an HTML form 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 an HTML form for editing the geocoder
PUT         /geocoder         Geocoders     update     update the one and only geocoder resource
DELETE         /geocoder         Geocoders     destroy   delete the geocoder resource

虽然资源是定义的单数,但匹配的控制器还是复数。

一个单资源路生成的辅助方法
·new_geocoder_url和new_geocoder_path映射了new方法路径
·edit_geocoder_url和edit_geocoder_path映射了edit方法路径
·geocoder_url和geocoder_path映射了show,create,update和destroy方法路径


3.6 定置资源
虽然约定的RESTful路由就能满足大多数的应用了,还是有几个自定义的方法让RESTful路由工作。这些选项包括:
·:controller
·:singular
·:requirements
·:conditions
·:as
·:path_names
·:path_prefix
·:name_prefix
·:only
·:except
还可以通过:member和:collection添加额外的资源。

3.6.1 使用:controller
:controller选项让我们在资源中使用一个不同的控制名称。比如:
map.resources :photos, :controller => "images"

URLs还是包含photo,但是路径请求就是images控制器了。
HTTP verb    URL    controller     action     used for
GET        /photos        Images     index         display a list of all images
GET        /photos/new        Images     new         return an HTML form for creating a new image
POST       /photos        Images     create         create a new image
GET        /photos/1        Images     show         display a specific image
GET        /photos/1/edit Images     edit           return an HTML form for editing a image
PUT        /photos/1        Images     update         update a specific image
DELETE     /photos/1        Images     destroy     delete a specific image
辅助方法会以资源的名称来生成,不是控制器的名称,所以在这个例子中,仍然得到的是photos_path, new_photo_path等方法。

3.7 控制器命名空间和路由
Rails允许你把保存在app/controllers路径下的文件夹通过命名空间把控制器分组。:controller选项就提供了一个方便的方法。比如,你有一个资源控制器是为admin用户准备的,保存在admin文件夹下。
map.resources :adminphotos, :controller => "admin/photos"
如果你使用控制器命令空间,必须Rails路由代码中一个微秒的地方:路由总是试图从上次的合理的请求保存更多的命名空间。例如,在视图中使用是adminphoto_path方法,生成一个链接<%=link_to "show", adminphoto(1) %>最终会生成这样的路径:admin/photos/show。但如,如果你有<%= link_to "show", {:controller => "photos", :action => "show"} %>代码,仍然会生成adminphoto_path,这是因为Rails将生成的show视图的URL关链到当前的URL.
如果要保证使用的是顶层的控制咕嘟,就要在控制器名称的前面加上斜线/。比如这样:
<%= link_to "show", {:controller => "/photos", :action => "show" } %>

可以通过:namespace选项提定命名空间:
map.resources :adminphotos, :namespace => "admin", :controller => "photos"

当联合使用with_options是相当有用的,这会把多个命名空间组织起来。
map.with_options(:namespace => "admin") do |admin|
admin.resources :photos, :videos
end

这和下的代码是相同的。
map.resources :photos, :namespace => "admin"
map.resources :videos, :namespace => "admin"

with_options就是可以把相同的部分取出来,减少重复,后面还会专门介绍。

3.7.1 :singular
如果没有足够的理由不要把复数的资源转换为单数,可以用:singular选项来重写复数的资源。
map.resources :teeth, :singular => "tooth"

3.7.2 :requirements
在RESTful路由中能够使用:requirements选项,强加一种格式给:id参数。例如:
map. resources :photos, :requiremets => {:id => /[A-Z][A-Z][0-9]+/}
这个声名就强制:id参数匹配一个正则表达式。所以在这个例子中,/photos/1将不能被识别,但是/photos/RR27就可以。

3.7.3 :conditions
在Rails路由里条件一般是用来设置HTTP verb的个别路由,理论上你可以这样设置它,但是在实践中这并不是一个好主意。(后面会有更多关于条件的讨论)

3.7.4 :as
:as选项会用新的名称重写标准的路径。例如:
map.resources :photos, :as => "images"
现在URLs就包含image了,但路由请求还是Photos控制器。
HTTP verb    URL    controller     action     used for
GET        /images        Photos     index         display a list of all photos
GET        /images/new        Photos     new         return an HTML form for creating a new photo
POST       /images        Photos     create         create a new photo
GET        /images/1        Photos     show         display a specific photo
GET        /images/1/edit Photos     edit           return an HTML form for editing a photo
PUT        /images/1        Photos     update         update a specific photo
DELETE     /images/1        Photos     destroy     delete a specific photo
辅助方法会以资源的名称来生成,不是路径的名称,所以在这个例子中,仍然得到的是photos_path, new_photo_path等方法。
这里要注意一下和:controller选项的区别。

3.7.5 :path_names
:path_names选项可以重写在URLs中自动生成的"new"和"edit"部分:
map.resources :photos, :path_names => {:new => 'make', :edit => 'change'}
URLs就变成下面这样了:
/photos/make
/photos/1/change
实际的方法名称没有改变,只是URLs变化了。

3.7.6 :path_prefix
:path_prefix选项在路径前面增加一个附件前缀。例如:在应用程序中图片属于某个摄影师,就可以这样声明路由:
map.resources :photos, :path_prefix => "/photographers/:photographer_id'
路径就是这样的:
/photographers/1/photos/2
/photographers/1/photos

3.7.7 :name_prefix
使用:name_prefix可以避免路由间的冲突。当你有两个相同名称的资源就可以使用:path_prefix映射为不同。例如:
map.resources :photos, :path_prefix => '/photographers/:photographer_id', :name_prefix => 'photographer_'
map.resources :photos, :path_prefix => '/agencies/:agency_id', :name_prefix => 'agency_'
这个联合就会得到像photographer_photos_path和agency_edit_photo_path这样的辅助方法。

3.7.8 :only和:except
默认情况下,Rails会根据默认的方法(index, show, new, create, edit, update和destroy)创建7个路径。可以使用:only和:except选项来调整这个行为。:only指定仅仅是要生成的路由。
map.resources :photos, :only => [:index, :show]
这个声明就,GET请注/photos就会成功,POST请求就会失败。

:except选项是指定哪个路由不被产生。
map.resources :photos, :except => :destroy

3.8 嵌套资源
一些资源通过有一些子资源。假如,应用程序中包含这些模型:
class Magazine < ActiveRecord::Base
has_many :ads
end

class Ad < ActiveRecord::Base
belongs_to :magazine
end
每一个ad属于一个magazine,嵌套路由来抓取这种关系,声明如下:
map.resources :magazines do |magazine|
magazine.resources :ads
end
快捷写法:
map.resources :magazines, :has_many => :ads
这个声明为ads创建路由:

HTTP verb     URL            controller action     used for
GET     /magazines/1/ads        Ads        index     display a list of all ads for a specific magazine
GET     /magazines/1/ads/new        Ads        new             return an HTML form for creating a new ad belonging to a specific magazine
POST     /magazines/1/ads        Ads        create     create a new ad belonging to a specific magazine
GET     /magazines/1/ads/1        Ads        show     display a specific ad belonging to a specific magazine
GET     /magazines/1/ads/1/edit Ads        edit     return an HTML form for editing an ad belonging to a specific magazine
PUT     /magazines/1/ads/1        Ads        update     update a specific ad belonging to a specific magazine
DELETE     /magazines/1/ads/1        Ads        destroy     delete a specific ad belonging to a specific magazine

创建了一些类似于magazine_ads_url和edit_magazine_ad_path的辅助方法。

3.8.1 :name_prefix
:name_prefix选项重写在嵌套路由辅助方自动生成的前缀。例如:
map.resources :magazines do |magazine|
magazine.resources :ads, :name_prefix => 'periodical'
end
将创建类似于periodical_ads_url和periodical_edit_ad_path的辅助方法。也可以使用:name_prefix去禁止前缀:
map.resources :magazines do |magazine|
magazine.resources :ads, :name_prefix => nil
end
将创建类似于ads_url和edit_ad_path的辅助方法。在调用这些方法时要注意提供id参数:
ads_url(@magazine)
edit_ad_path(@magazine, @ad)

3.8.2 :has_one和:has_many
:has_one和:has_many为简单的嵌套路由提供了简洁的标记。使用:has_one嵌套单资源,:has_many嵌套多资源:
map.resources :photos, :has_one => :photographer, :has_many =>[:publications, :versions]
这和下现的声名是一样的:
map.resources :photos do |photo|
photo.resource :photographer
photo.resources :publications
photo.resources :versions
end

3.8.3 限制嵌套
如果你愿意,嵌套资源里还可以再嵌套资源,比如:
map.resources :publishers do |publisher|
publisher.resources :magazines do |magazine|
    magazine.resources :photos
end
end
然而,没有使用name_prefix => nil,深度嵌套资源会带来麻烦,在这个例子中,会有像下面这样的URLs产生:
/publishers/1/magazines/2/photos/3
对应的辅助方法是publisher_magazine_photo_url,需要指定三层对象,确实不是一个好的设计,好的思想是让资源嵌套不要超过一层。

3.8.4 浅层嵌套
:shallow选项的使用有效的解决了深层嵌套的困难。可以在任何层级指定这个选项,然后嵌套资源路径会参考特定成员(也就是有:id的参数)将不再使用父路径前缀和名称前缀。看看这意味着什么,考虑一下下面的路由设置:
map.resources :publishers, :shallow => true do |publisher|
publisher.resources :magazines do |magazine|
    magazine.resources :photos
end
end
这个就会识别像下面这样的路由:
/publishers/1   ==> publisher_path(1)
/publishers/1/magazines ==> publisher_magazines_path(1)
/magazines/2 ==> magazine_path(2)
/magazines/2/photos ==> magazines_photos_path(2)
/photos/3 ==> photo_path(3)

如果你愿意,也可以结合:has_one和:has_many选项使用:shallow

map.resources :publishers, :has_many => {:magazines => :photos}, :shallow => true

3.9 从数组中生成路由
Rails能从一个数组参数中生成RESTful路由。比如,有下面这样的声名:
map.resources :magazines do |magazine|
magazine.resources :ads
end

Rails将生成像magazine_ad_path这样的辅助方法,你可以这样使用它:
<%= link_to "Ad details", magazine_ed_path(@magazine, @ad) %>
用对象的数组可以同样的路由:
<%= link_to "Ad details", [@magazine, @ad] %>

3.10 命名空间资源
使用:path_prefix和:name_prefix可以做些复杂的事情。比如,能够结合这两者的使用,把管理资新移动到他们自己的文件夹:
map.resources :photos, :path_prefix => 'admin', :controller => 'admin/photos'
map.resources :tags, :name_prefix => 'admin_photo_', :path_prefix => 'admin/photos/:photo_id', :controller => 'admin/photo_tags'
map.resources :ratings, :name_prefix => 'admin_photo_', :path_prefix => 'admin/photos/:photo_id', :controller => 'admin/photo_ratings'
一个好消息是你可以停止使用这么复杂的层级关系了。Rails提供了命名空间资源,声名如下:
map.namespace(:admin) do |admin|
admin.resources :photos,
:has_many => {:tags, :ratings}
end
正如你看到的,使用命名空间资源更简洁。比如:你将得到admin_photos_url,对应的控制器是Admin::PhotosController,并匹配admin/photos, admin_photos_ratings_path匹配/admin/photos/_photo_id_/ratings,对应的控制器是Admin::RatingsController。尽管你没有指定路径前缀,路由代码也会加上去。

3.11 添加更多的RESTful行为
你并没有受到RESTful默认的七个路由的限制,能够添加member路由(应用资源的单个实例),添加new路由(应用于创建一个新资源),或者添加collection routes(应用于资源的集合)。

3.11.1 添加Member路由
使用:member选项添加member路由:
map.resources :photos, :member => {:preview => :get}
Rails会生成路径/photos/1/preview,使用GET请求。也会创建一个preview_photo的路由辅助方法。
在member路由哈希中,每个路由可以提定一个HTTP请求方式,可以使用 :get, :put, :post, :delete, 或者:any,如果你需要多个请求方式就把它们放在数组中:
map.resources :photos, :member => {:prepare => [:get, :post]}

3.11.2 添加Collection路由
使用:collection选项添加collection路由:
map.resources :photos, :collection => {:search => :get}
Rails会生成路径/photos/search,使用GET请求。也会创建一个search_photos的路由辅助方法。
和member路由一样,collection路由也可以指定数组:
map.resources :photos, :collection => {:search => [:get, :post]}

3.11.3 添加New路由
使用:new选项添加new路由:
map.resources :photos, :new => {:upload => :post}
Rails会生成路径/photos/upload,使用POST请求。也会创建一个upload_photos的路由辅助方法。

如果想给标准的方法重新定义请求方式,可以提明方法的映射。例如:
map.resources :photos, :new => {:new => :any}
就将允许new方法,被请求到photos/new,和哪种HTTP请求方式没有关系。

 

4 正则路由
Rails支持正则路由——是把URLs映射到控制器和方法上。可以应用程序中使用RESTful路由和正则路由两种风格。建议多使用RESTful风格,因为这更容易编写和阅读。

4.1 Bound Parameters
设置正则路由的时候,:controller映射控制器的名称,:action映射方法的名称。例如默认的Rails路由:
map.connect ':controller/:action/:id'
如果请求是/photos/show/1,就会和这个路由做相当的匹配,就会是调用Photos控制器中的show方法,并把这个:id传入params[:id]。

4.2 通配符组件
如果你愿意,可以设置很多的通配符号:
map.connect ':controller/:action/:id/:user_id'
请求的URL是/photos/show/1/2,就会调用Photos控制器中的show方法,params[:id]就设为1,params[:user_id]就设为2。

4.3 静态文本
当创建一个路由时,可以指定一个静态的文本,例如:
map.connect ":controller/:action/:id/with_user/:user_id"
应答的路径就像这样/photos/show/1/with_user/2

4.4 Querystring参数
Rails路由在params哈希中自动选出querystring参数。例如:
map.connect ':controller/:action/:id'
/photos/show/1?user_id=2这个路径进来,就会分配Photos控制器的show方法来处理,params[:id]设为1,params[:user_id]设为2。

4.5 定义默认值
给:controller和:action设定默认值:
map.connect 'photos/:id', :controller => 'photos', :action='show'
就样可以匹配这样的路径:/photos/12

还可以使用:defaults选项:
map.connect 'photos/:id', :controller => 'photos', :action => 'show', :defaults => { :format => 'jpg' }
就同样匹配这样的路径:/photos/12,并且params[:format]设为jpg。

4.6 命名路由
命名路由就不使用connect方法了,可以使用其他的名称来创建一个命名路由。例如:
map.logout '/logout', :controller => 'sessions', :action => 'destroy'
这个路会做两件事,首先,/logout请求将被发送给Sessions控制器的destroy方法。Rails会生成logout_path和logout_url辅助方法。

4.7 路由需求
使用:requirements选项为路由参数指定一种格式:
map.connect 'photo/:id', :controller => 'photos', :action => 'show', :requirements => { :id => /[A-Z]/d{5}/ }
这个路由应答像/photo/A12345这样的URLs。也可以简写:
map.connect 'photo/:id', :controller => 'photos', :action => 'show', :id => /[A-Z]/d{5}/

4.8 路由条件
可以用:conditions选项来约束路由,现在来约束:method:
map.connect 'photo/:id', :controller => 'photos', :action => 'show', :conditions => {:method => :get}
在RESTful路由使用:conditions,可以具体指定:get, :post, :put, :delete, 或者 :any给:method。

4.9 路由通配符
路由通配符是一种指定一个详细的参数去匹配路由剩余部分的方法。例如:
map.connect 'photo/*other', :controller => 'photos', :action => 'unknow'
这个路由会匹配photo/12或者/photo/long/path/to/12,并且为params[:other]创建一个数组。

4.10 路由选项
使用:with_options把相似路由分成一组:
map.with_options :controller => 'photo' do |photo|
photo.list '', :action => 'index'
photo.delete ':id/delte', :action => 'delete'
photo.edit ':id/edit', :action => 'edit'
end


5 格式和respond_to
使用:format参数让路由可以根据不同的HTTP请求格式做不同的处理:
map.connect ':controller/:action/:id.:fromat'
匹配/photo/edit/1.xml或者/poto/show/2.rss。在控制器的方法中,能够对不同的请求作出应答:
respond_to do |format|
format.html
format.xml {render :xml => @photo.to_xml}
end

5.1 用Http Header指定格式
如作没有:format参数,Rails将自动认为是HTTP Accept格式。

5.2 元类型
默认情况下,Rails能对html, text, json, csv, xml, rss, atom和yaml做出应答。如果还需要其他类型,可以在环境中注册它:
Mime::Type.register "image/jpg", :jpg


6 默认路由
当创建一个Rails应用时,routes.rb就会自动生成两个默认路由:
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
如果你不使用RESTful风格的路由,这个路由为很多URLs就提供了合理的默认值。

7 空路由
不要和默认路由混淆,空路由有它明确的目的:为web站点指定根路径。以example.com为例,以http://example.com或者http://example/来请求,就会由空路由来匹配。

7.1 使用map.root
使用map.root设定空路由:
map.root :controller => 'pages', :action => 'main'使用root方法,是告诉Rails就是请求站点的根路径。

为了更方便阅读, 可以指定一个已经创建好的路由给map.root:
map.index 'index', :controller => 'pages', :action => 'main'
map.root :index

由于是从上至下处理文件,所以必须在调用map.root指定命名路由。

7.2 连接空字符串
也可以使用连接空字符串来指定空路由:
map.connect '', :controller => "pages", :action => "main"

假若指定了空路由,但发现它好像并没有工作,首页还是Rails的默认页面,那么请删除public/index.html文档后再试。

8 检查和测试路由
在应用程序中,路由不应该是个黑盒子,它对你是永远开放的。Rails为检查和测试路由提供了内建的工具。

8.1 用rake查看存的路由
在应用程序中,如果想查看路由列表,可以运行rake routes。它会列出你在routes.rb定义的全部路由。
         users GET /users          {:controller=>"users", :action=>"index"}
formatted_users GET /users.:format {:controller=>"users", :action=>"index"}
                POST /users          {:controller=>"users", :action=>"create"}
                POST /users.:format {:controller=>"users", :action=>"create"}

8.2 测试路由
Rails内建了三个断言来做路由测式
·assert_generates
·assert_recognizes
·assert_routing

8.2.1 assert_generates
使用assert_generates断言一个设置选项生成的特定路径。
assert_generates "/photos/1", { :controller => "photos", :action => "show", :id => "1" }
assert_generates "/about", :controller => "pages", :action => "about"
还可以提供:method参数:

8.2.2 assert_recognizes
assert_recognizes断言反转的assert_generates。在应用程序中它断言Rails认可的给定的路径和路由到一个特定的位置。
assert_recognizes { :controller => "photos", :action => "show", :id => "1" }, "/photos/1"
可以使用:method参数:
assert_recognizes { :controller => "photos", :action => "create" }, { :path => "photos", :method => :post }
能够使用RESTful辅方法测试RESTful路由:
assert_recognizes new_photo_url, { :path => "photos", :method => :post }

8.2.3 assert_routing
assert_routing是断言检测路径生成选项和选项生成路径,它是assert_generates和assert_recognizes的联合:
assert_routing { :path => "photos", :method => :post }, { :controller => "photos", :action => "create" }

你可能感兴趣的:(Rails路由)