深入浅出说路由
这份教程介绍了 Rails 路由常用的特性。通过本文,你将会能够:
- 了解 routes.rb 中的代码。
- 用 Resourceful 风格或者 tt>match 方法 构建你的路由
- 了解一个 action 是如何接收从路由来的参数。
- 用路由提供的 helper 来生成 URL 路径。
- 一些更高级的技巧,例如 constraints and Rack endpoints。
1 Rails 路由的意义
Rails 路由能够识别 URL 并且将请求分发到对应的 控制器下的 action 中。 它也可以生成相对路径和 URL 来避免在你的视图中的硬编码(hardcode string).
1.1 链接 URL 和代码
当你的 Rails 应用接收到一个像这样的请求时
GET /patients/17
|
它就会尝试着让路由去匹配字符串到一个控制器行为中。如果第一条路由规则是这样的:
match
"/patients/:id"
=>
"patients#show"
|
这个请求将会被分发到 patients 控制器下面的 show action 中去,同时 { :id => “17” } 将会存储在 params 中。
1.2 用代码生成 URL 和 路径
你同样可以用路由来生成 URl 和路径,如果你的应用包含了这样的代码:
@patient
= Patient.find(
17
)
|
<%=
link_to
"Patient Record"
, patient_path(
@patient
)
%>
|
路由将会自动生成一个路径 /patients/17 ,这将使你的代码更加简洁易懂而健壮。注意这里我们没必要将 id 传递给路由 Helper。
2 Resource 路由规则: Rails 之道
Resource 路由规则能让你迅速地构建出 resourceful 的控制器所需常用路由规则,它可以自动地为你生成 index, show, new, edit, create, update 和 destroy 行为,而一个 Resourceful 的路由声明只需要一行代码。
2.1 web 与 Resource
浏览器向 Rails 的一个页面发起请求 URL 的时候必须要指定一个 HTTP 请求方法,例如 GET, POST, PATCH, PUT and DELETE.这些方法将会对 Rails 中的资源进行不同的操作。而一个 Resource 的路由规则将会为一个控制器自动匹配多种相关方法。
例如,当你的 Rails 程序接收到了这样的请求:
DELETE /photos/17
|
它会向 路由请求匹配到一个控制器行为上,这里假设匹配上了我们路由上的这条规则
resources
:photos
|
Rails 就将会把这个请求分发到 photos 控制器下的 destroy 方法下,并且把 { :id => “17” } 作为 params 的值.
2.2 CRUD, Verbs, Actions
对于 Rails 一个 Resourceful 的路由提供了一组 HTTP 和 控制器行为的对应关系。它还约定了与每个行为相对应的 CRUD 的数据库操作。例如这样的一个 简单的 路由记录:
resources
:photos
|
将会在应用中生成七个不同的路由记录,他们都会匹配到 Photos 控制器上去。
HTTP Verb | Path | action | used for |
---|---|---|---|
GET | /photos | index | display a list of all photos |
GET | /photos/new | new | return an HTML form for creating a new photo |
POST | /photos | create | create a new photo |
GET | /photos/:id | show | display a specific photo |
GET | /photos/:id/edit | edit | return an HTML form for editing a photo |
PATCH/PUT | /photos/:id | update | update a specific photo |
DELETE | /photos/:id | destroy | delete a specific photo |
Rails 路由的匹配是按照规则顺序匹配的,所以如果在你的路由规则中有一句 resources :photos,接着一句 get 'photos/poll', 这样的话 resources 定义的 show 这个行为会比 get 方法优先完成匹配。如果这样并非是你的本意,你只要将 get 'photos/poll' 调到 resources 那行的前面就可以先行指定特别的匹配规则。
2.3 URL 和 Path
建立一个 Resourceful 的路由的同时也会为你的应用自动添加一系列的 Helper 方法。以上面的 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) returns /photos/10)
同时,这些 _path Helper 方法还有一个对应的 _url Helper (例如 photos_url) 后者将会返回包含了主机,端口等信息的绝对路径地址。
因为路由器可以使用 HTTP 动词 加上一个 URL 来匹配受到的请求,所以四个不同的 URL 可以组合出7种不同的行为。
2.4 同时定义多个 Resource
如果你想在路由表中建立多个 Resource, 你可以把他们写到一行里面去,这样只需要调用一个 resources ,同时为你节约点输入时间:
resources
:photos
,
:books
,
:videos
|
它和下面这样写的效果是一样的:
resources
:photos
resources
:books
resources
:videos
|
2.5 单件 Resources
有时候,你并不需要一个能够根据ID查询个体的 resource 。例如,你可能会想用 /profile 来显示当前登录用户的简介,这时候你可以用 map /profile (或者 /profile/:id) 一个单体化的 Resource 来匹配 show 行为。
match
"profile"
=>
"users#show"
|
或者这样来声明一个 Resourceful 的资源:
resource
:geocoder
|
它会为你的应用建立六个不同的 routes ,都将匹配到 Geocoders 控制器:
HTTP Verb | Path | action | used for |
---|---|---|---|
GET | /geocoder/new | new | return an HTML form for creating the geocoder |
POST | /geocoder | create | create the new geocoder |
GET | /geocoder | show | display the one and only geocoder resource |
GET | /geocoder/edit | edit | return an HTML form for editing the geocoder |
PATCH/PUT | /geocoder | update | update the one and only geocoder resource |
DELETE | /geocoder | destroy | delete the geocoder resource |
因为你可能要在路由中同时使用单数 (/account) 和 复数 (/accounts/45) 而想要对应同一个控制器,所以单件 Resource 命令也是对应复数的控制器。
一个 单件 Resourceful 路由生成这些 Helper 方法:
- new_geocoder_path 返回 /geocoder/new
- edit_geocoder_path 返回 /geocoder/edit
- geocoder_path 返回 /geocoder
同复数的 resources 命令一样,以 _url 结尾的 Hepler 将会包括主机名,端口值和其他的相关路径前缀。
2.6 控制器、命名空间(Namespaces),和路由
有时,你可以想要把一些同类的控制器组织到一个命名空间下面去,其中最常见的就是我们会把一组管理功能的控制器放到 Admin:: 下,然后把这个空间下的控制器都放在 app/controllers/admin 目录下,并且统一在路由中配置:
namespace
:admin
do
resources
:posts
,
:comments
end
|
这样将会为每一个 posts 和 comments 方法建立一个路由规则,对于 Admin::PostsController, Rails 会建立这些路由表规则:
HTTP Verb | Path | action | named helper |
---|---|---|---|
GET | /admin/posts | index | admin_posts_path |
GET | /admin/posts/new | new | new_admin_post_path |
POST | /admin/posts | create | admin_posts_path |
GET | /admin/posts/:id | show | admin_post_path(:id) |
GET | /admin/posts/:id/edit | edit | edit_admin_post_path(:id) |
PATCH/PUT | /admin/posts/:id | update | admin_post_path(:id) |
DELETE | /admin/posts/:id | destroy | admin_post_path(:id) |
如果你想要把路由 /posts (不以 /admin 作前缀) 匹配到 Admin::PostsController,你就需要这样了:
scope
:module
=>
"admin"
do
resources
:posts
,
:comments
end
|
或者对单个 Resource 匹配的时候
resources
:posts
,
:module
=>
"admin"
|
而你如果想要把路由:/admin/posts 给匹配到 PostsController (控制器中没有 Admin:: 这个模块作为前缀),你可以用
scope
"/admin"
do
resources
:posts
,
:comments
end
|
或者在为单个 Resource 匹配的时候这么写:
resources
:posts
,
:path
=>
"/admin/posts"
|
在这些例子里面,可以发现 scope 并不会改变 routes 的生成的路径名称的(译者注:named routes,这里指的是生成的Helper的名称。) ,例如在最后一个例子里面,会生成匹配到 PostsController的规则:
HTTP Verb | Path | action | named helper |
---|---|---|---|
GET | /admin/posts | index | posts_path |
GET | /admin/posts/new | new | new_post_path |
POST | /admin/posts | create | posts_path |
GET | /admin/posts/:id | show | post_path(:id) |
GET | /admin/posts/:id/edit | edit | edit_post_path(:id) |
PATCH/PUT | /admin/posts/:id | update | post_path(:id) |
DELETE | /admin/posts/:id | destroy | post_path(:id) |
2.7 嵌套(Nested)数组
通常,一个 Resource 常常有数个逻辑上的子 Resource 。例如,你的应用有这样一个模型:
class
Magazine < ActiveRecord::Base
has_many
:ads
end
class
Ad < ActiveRecord::Base
belongs_to
:magazine
end
|
Nested routes allow you to capture this relationship in your routing. In this case, you could include this route declaration:
resources
:magazines
do
resources
:ads
end
|
在这里,对于每一个 magazines 的路径下面都需要能够有 ad 作为 Resource 匹配到AdsController。 这时候每一组 ad 都需要指定一个 magzine 作为前缀。
HTTP Verb | Path | action | used for |
---|---|---|---|
GET | /magazines/:magazine_id/ads | index | display a list of all ads for a specific magazine |
GET | /magazines/:magazine_id/ads/new | new | return an HTML form for creating a new ad belonging to a specific magazine |
POST | /magazines/:magazine_id/ads | create | create a new ad belonging to a specific magazine |
GET | /magazines/:magazine_id/ads/:id | show | display a specific ad belonging to a specific magazine |
GET | /magazines/:magazine_id/ads/:id/edit | edit | return an HTML form for editing an ad belonging to a specific magazine |
PATCH/PUT | /magazines/:magazine_id/ads/:id | update | update a specific ad belonging to a specific magazine |
DELETE | /magazines/:magazine_id/ads/:id | destroy | delete a specific ad belonging to a specific magazine |
这样生成的路由 Hepler 就会是 magazine_ads_url 和 edit_magazine_ad_path。这个帮助方法会把第一个参数当作指定 Magazine 的实例(例如 magazine_ads_url(@magazine))。
2.7.1 嵌套 Resources 的限制
你可以把多个 Resources 一起嵌套起来。例如:
resources
:publishers
do
resources
:magazines
do
resources
:photos
end
end
|
但是多层的嵌套很快会让你的路由变得笨重。例如上面那个例子里,你需要这样来访问一个照片:
/publishers/1/magazines/2/photos/3
相关的路由 helper 会变成 publisher_magazine_photo_url ,而且需要在参数中传入指定的每一个实例。事实上,这种令人郁闷的情形已经被很多人讨论过了,Jamis Buck 在 这里 有一份很火的 Rails 路由设计简则 ,这个问题或许可以在那里找到答案。
事实上,你无法建立超过一层深的嵌套 Resources.
2.8 从对象到路径 URL
关于 路由 Helper, 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 这个 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
]
%>
|
这样就能把你的模型和 URL 有效的结合起来了,而且这也是一个很棒很 Resourceful 的特性。
2.9 加入额外的 RESTful 行为
除了7个默认创建的 RESTful 路由之外,如果你喜欢,你可以将一些额外的路由应用到你的物件集合或者单个的物件下面。
2.9.1 成员(Member)路由
加入一个成员路由只需要在你的 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
|
2.9.2 集合(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
|
2.9.3 注意事项
如果你发现自己对 Resourceful 路由加入了太多的额外规则,是时候停下来好好审视下你是不是应该创建另一个 resource。
3 非 Resourceful 路由
对于 Resourceful 的路由,Rails 有着一整套强大的路由和行为支持,即使如此,你有时候可能也会需要一些不是由 Rails 成组生成的路由,这时候,你就需要把规则手动地一个一个输入到你的应用中去了。
虽然 Resourceful 的路由规则功能强大,但是在很多时候使用一个简单路由规则更为合适。如果你觉得简单路由规则更合适,你并没有必要把应用中的每个规则都往 Resourceful 框架上套。
特别是在你为一个控制器添加上了新的行为的时候,添加一条简单路由规则往往更方便一些。
3.1 相关参数
当你为你的路由设置了一条规则的时候,你就需要提供一组符号字面量来让 Rails 能更好的识别并匹配收到的 HTTP 请求。其中有两个符号量尤其的特别::controller 位置会匹配到你应用中名字对应的控制器,:action 有匹配到你应用中对应的控制器中的行为。举个例子,下面这个默认的 Rails 路由规则:
match
':controller(/:action(/:id))'
|
如果收到的请求是 /photos/show/1 (前提是它还没有被其他的前面的路由规则匹配),那么按照这个规则 Rails 将会调用 PhotosController 控制器中的 show 行为,然后把后面的参数 "1" 放到 params[:id] 中以供使用。因为 :action 和 :id 被圆括号包围,所以,他俩可以被忽略,所以这条规则同样能够将 /photos 匹配到 PhotosController#index 。
3.2 动态部分
你可以设置一条路由规则有任意多的动态部分,除了:controller 或 :action ,其他的片段都将会被转换成 params 的一部分。如果你写了这样的一条路由:
match
':controller/:action/:id/:user_id'
|
如果收到了 /photos/show/1/2 这样的请求,将会分发到 PhotosController 下的 show 行为中,而 params[:id] 会是 "1", params[:user_id] 是 "2".
当你使用了 :controller 来匹配路径时你就无法使用 :namespace 和 :module 了。如果你需要像 Resourceful 路由规则一样使用它就需要加上一些正则条件,例如:
match
':controller(/:action(/:id))'
,
:controller
=> /admin\/[^\/]+/
|
默认情况下 动态部分无法接收句点————这是因为“点”这个符号已经被用作了匹配类型的分隔符。如果你需要在规则的动态部分中使用句点你就需要加入一些特殊的正则条件来覆写原本的匹配方法 ———— 例如:id => /[^\/]/ 将会让你的 :id+ 能够匹配除了 斜杠以外的任何字符。
3.3 静态部分
你可以在创建路由规则的时候指定一个静态的部分:
match
':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 字符串队列
The params will also include any parameters from the query string. For example, with this route:
match
':controller/:action/:id'
|
如果接收到的路径为 /photos/show/1?user_id=2 ,那么 Rails 会把它分发到 Photos 控制器下面的 show 行为中去,params 会是 { :controller => “photos”, :action => “show”, :id => “1”, :user_id => “2” }.
3.5 设置默认值
如果你需要一个默认的匹配值,你可以用一个没有指定 :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"。
3.6 命名路由
你可以用 :as 参数来定义一个路由的名字。
match
'exit'
=>
'sessions#destroy'
,
:as
=>
:logout
|
这样在应用程序中产生的 Helper 就将会是 create logout_path 和 logout_url。而调用了 logout_path 就会返回 /exit 路径。
3.7 HTTP 动词约定
你可以用 :via 选项来限定一个请求能相应的一个或者多个 HTTP 方法:
match
'photos/show'
=>
'photos#show'
,
:via
=>
:get
|
简写作:
get
'photos/show'
|
你也可以将多个动词行为绑定到一条路由规则上去:
match
'photos/show'
=>
'photos#show'
,
:via
=> [
:get
,
:post
]
|
3.8 部分正则约定
你可以使用 :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'
|
3.9 请求限制
您还可以在原有路由规则的基础上限定任何的Request的对象,它将返回 String。
你可以用一个 constraint 关键字指定一个请求限制:
match
"photos"
,
:constraints
=> {
:subdomain
=>
"admin"
}
|
你也可以用代码块的形式来体现这样的限制
namespace
:admin
do
constraints
:subdomain
=>
"admin"
do
resources
:photos
end
end
|
3.10 高级限定
如果你需要更为高级的限定,你只要为 Rails 提供一个能相应 matches? 方法的对象就可以。例如你想要把所有在黑名单中的用户都被路由过滤到 BlacklistController 里面去。 你可以:
class
BlacklistConstraint
def
initialize
@ips
= Blacklist.retrieve_ips
end
def
matches?(request)
@ips
.include?(request.remote_ip)
end
end
TwitterClone::Application.routes.draw
do
match
"*path"
=>
"blacklist#index"
,
:constraints
=> BlacklistConstraint.
new
end
|
3.11 通配路由匹配
通配路由规则能够指定一个参数让任意部分匹配。例如:
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".would match zoo/woo/foo/bar/baz with params[:a] equals "zoo/woo", and params[:b] equals "bar/baz".
从 Rails 3.1 开始,通配路由总是会自动地将格式字符串默认匹配,例如下面这个路由:
match
'*pages'
=>
'pages#show'
|
如果你发起了这样的一个请求,你的参数 params[:pages] 将会是 "foo/bar" ,而你的请求格式会识别为 JSON 。 如果你想要在旧的 3.0.x 中有同样的特性,你需要提供一个散列参数:format => false,像这样
match
'*pages'
=>
'pages#show'
,
:format
=>
false
|
如果你想要强制地被指定一种格式,(其无法被忽略),你可以像这样加上 :format => true 参数:
match
'*pages'
=>
'pages#show'
,
:format
=>
true
|
3.12 重定向
你可以在路由规则中通过 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 标志 “永久重定向”。在某些浏览器或者代理服务器中这个标识码将会使旧的页面失效。
在这些例子里,如果你事先没有为 Rails 提供一个主机地址,Rails 将会给予当前请求地址进行细节的处理。
3.13 Routing to Rack Applications
Instead of a String, like "posts#index", which corresponds to the index action in the PostsController, you can specify any Rack application as the endpoint for a matcher.
match
"/application.js"
=> Sprockets
|
As long as Sprockets responds to call and returns a [status, headers, body], the router won’t know the difference between the Rack application and an action.
For the curious, "posts#index" actually expands out to PostsController.action(:index), which returns a valid Rack application.
3.14 使用 root
你可以用 root 方法来指定 Rails 如何匹配 "/" 路由:
root
:to
=>
'pages#main'
root
'pages#main'
# shortcut
for
the above
|
你可以将 root 规则放在文件的最上方,这样这条规则能第一个进行匹配,因为这恐怕将会是最常用的的规则了。你同样需要删除文件 public/index.html 来让这个规则发挥效用
4 定制 Resourceful 的路由规则
虽然通过 resources :posts 生成的默认的路由和 Helper 方法给你带来了很大的方便。你可能还是需要从某些时候定制这样的路由规则。 Rails 允许你用某种方法定制或者仅生成部分Helper。
4.1 指定使用控制器。
:controller 选项能够让你选择与 resource 关联的控制器。例如:
resources
:photos
,
:controller
=>
"images"
|
它将会接收 /photos开头的路径然后匹配到 Images 控制器上:
HTTP Verb | Path | action | named helper |
---|---|---|---|
GET | /photos | index | photos_path |
GET | /photos/new | new | new_photo_path |
POST | /photos | create | photos_path |
GET | /photos/:id | show | photo_path(:id) |
GET | /photos/:id/edit | edit | edit_photo_path(:id) |
PATCH/PUT | /photos/:id | update | photo_path(:id) |
DELETE | /photos/:id | destroy | photo_path(:id) |
使用 photos_path, new_photo_path,等等 生成路径到这个 resource 上。
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
|
当然,你也可以像在 非resourceful 路由规则中运用正则一样在这里使用各种高级的正则或限定技巧。
默认情况下 :id 参数不接收 点 ———— 这是因为 点已经被用作了格式分割符。如果你需要在 :id 中的正则中匹配出 点,你需要这样复写正则表达式,例如::id => /[^\/]+/ ,这样会允许除了斜杠之外的任何字符。
4.3 覆盖 命名(Named) Helper
:as 选项能让你把默认命名的路由 Helper 重命名成定制的字符串。例如:
resources
:photos
,
:as
=>
"images"
|
这将会把 /photos 相关的路由请求匹配到 PhotosController ,但是产生的 Helper 却是 :as 选项中的值。
HTTP verb | Path | action | named helper |
---|---|---|---|
GET | /photos | index | images_path |
GET | /photos/new | new | new_image_path |
POST | /photos | create | images_path |
GET | /photos/:id | show | image_path(:id) |
GET | /photos/:id/edit | edit | edit_image_path(:id) |
PATCH/PUT | /photos/:id | update | image_path(:id) |
DELETE | /photos/:id | destroy | image_path(:id) |
4.4 覆盖 new 和 edit 片段
:path_names 选项能够让你把自动生成路径中的 “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
# rest of your routes
end
|
4.5 为命名(named) 路由 Helper 加上前缀
你可以使用 :as 选项来为 命名路由的 Helper 加上一个前缀。这样可以防止Helper 名和其他的名字冲突。
scope
"admin"
do
resources
:photos
,
:as
=>
"admin_photos"
end
resources
:photos
|
这样就将会产生 admin_photos_path,new_admin_photo_path 这样的路由 Helper。
在 scope 后面用上 :as 参数,就可以为一组路由规则修改前缀:
scope
"admin"
,
:as
=>
"admin"
do
resources
:photos
,
:accounts
end
resources
:photos
,
:accounts
|
这样将会建立像 admin_photos_path 和 admin_accounts_path 这样的 helper ,他们分别对应 /admin/photos and /admin/accounts 。
namespace scope 将会自动为你添加 :as , :module 和 :path 前缀。
你甚至还能为你的路由规则添加上一个 命名参数(named parameter):
scope
":username"
do
resources
:posts
end
|
这样将会让你 匹配上 /bob/posts/1 这样的路径,并且在控制器,视图和 Helper 中会将 username 对应到 params[:username] 上。
4.6 对建立的路由进行限制
默认地, Rails 会在应用中为每一个 RESTful 路由建立7个动作 (index, show, new, create, edit, update, and destroy)。不过你可以用 :only 或 :except 选项来针对你的路由进行调整:
resources
:photos
,
:only
=> [
:index
,
:show
]
|
这样, 对于 /photos 的一个 GET 请求将会成功,但是对 /photos 发起的 POST 请求(原本对应的是 create 动作)将会失败。
:except 选项指定了你所_不_需要创建的动作。
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 verb | Path | action | named helper |
---|---|---|---|
GET | /kategorien | index | categories_path |
GET | /kategorien/neu | new | new_category_path |
POST | /kategorien | create | categories_path |
GET | /kategorien/:id | show | category_path(:id) |
GET | /kategorien/:id/bearbeiten | edit | edit_category_path(:id) |
PATCH/PUT | /kategorien/:id | update | category_path(:id) |
DELETE | /kategorien/:id | destroy | category_path(:id) |
4.8 重定义单复数
如果你想要为一个 resource 定义一个单数格式,你只要为 Inflector 增加上一个 额外的规则就可以了。
ActiveSupport::Inflector.inflections
do
|inflect|
inflect.irregular
'tooth'
,
'teeth'
end
|
h4 在内嵌 Resource 中使用 :as 参数
在内嵌的 Resource 中使用 :as 参数将会覆盖掉自动生成部分的 Helper 名。例如:
resources
:magazines
do
resources
:ads
,
:as
=>
'periodical_ads'
end
|
这将会建立像 magazine_periodical_ads_url 和 edit_magazine_periodical_ad_path 这样的 Helper。
5 对路由进行检查和测试
Rails 提供了检查和测试路由的工具。
5.1 通过 rake 查看存在的路由规则
如果你想要一整套应用的完整可用的路由规则列表,运行 rake routes 命令就好。他将会根据你在 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=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"
})
|
关于 Rails Guides 中文
你也可以为 Rails Guides 中文做出贡献.
如果你发现了任何翻译上的错误或者不足.请到 GitHub 帐号 clone 并且 fork 一份进行更正提交,我们会很快做出回应。 如果你发现了任何代码上的 bug 或者不足,你可以到 docrails 进行反馈与更正。 如果你发现了某一篇文章已经过时了并且并没有做出相应的提示,你也同样可以在 这里 上发起一个请求来提醒我们。
如果对 Rails 有什么建议的话去 这里.
当然你不管你有什么想要说的都可以发到 Ruby China 讨论或者 mail我.欢迎加入到 Ruby on Rails 社区!