Rails路由系统:定义、定制与使用
在Rails应用程序中,路由系统起着至关重要的作用,它负责将传入的请求映射到相应的控制器和动作。下面我们将详细探讨Rails路由系统的各个方面。
1. 使用map.connect定义路由
创建新的Rails应用程序时,会生成一个
config/routes.rb
文件,其中定义了两条默认(未命名或匿名)路由:
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
路由子系统会尝试在定义的模式和传入请求的URL之间找到匹配项。例如,对于第一条路由
map.connect ':controller/:action/:id'
,如果请求的URL是
/library/borrow/25189
,Rails会将URL中的第一个令牌(
library
)映射到
:controller
参数,第二个令牌(
borrow
)映射到
:action
参数,第三个令牌(
25189
)映射到
:id
参数。此时,
params
对象如下:
params = { :controller => "library", :action => "borrow", :id => "25189" }
Rails将实例化
library
控制器并执行其中定义的
borrow
动作来处理请求。作为开发者,可以通过
params[:id]
在控制器中获取
:id
参数。
默认参数
并非所有三个参数都是严格必需的。例如,省略
id
,请求路径为
/library/catalog
时,参数如下:
params = { :controller => "library", :action => "catalog" }
即使缺少第三个参数,这种匹配也是可能的,因为
connect
为
:id
定义了默认值。除非在URL中指定了值,否则
:id
为
nil
,并且
nil
参数不会包含在
params
对象中。同样,当缺少动作时,
connect
定义的默认值为
"index"
,因此
/library
将映射到
{ :controller => "library", :action => "index" }
。
2. 定制路由
默认路由比较通用,其模式可以匹配包含任意控制器名称、任意动作名称和任意
id
的请求URL,并且支持任何HTTP动词。但它并非能匹配所有路径,例如
/library/borrow/43274/something
或
/library/something/borrow/43274
就无法匹配,因为它们有四个令牌,而不是预期的三个。
:id
参数可以有任何值,不一定局限于字符串形式的数字,不过由于
params[:id]
通常会传递给
ActiveRecord::Base
的
find
方法,所以经常是数字。
:controller
和
:action
是特殊命名的参数,分别用于标识处理请求的控制器和动作。模式中的其他符号,包括
:id
,都将被视为常规命名参数并包含在
params
对象中。
可以通过向
connect
方法传递选项(如
:controller
或
:action
)来显式要求特定的控制器和/或动作。例如:
map.connect 'library/:action/:isbn', :controller => "library"
对于请求路径
/library/borrow/9780470189481
,将映射为:
params = { :controller=> "library", :action=> "borrow", :isbn=> "9780470189481" }
该路由也会匹配
/library/archive/9780470189481
,映射结果为:
params = { :controller=> "library", :action=> "archive", :isbn=> "9780470189481" }
这里动作是可变的,但控制器是固定的,请求路径必须以
/library/
开头,后面跟着动作和ISBN。
:id
默认为
nil
,但自定义命名参数
:isbn
没有默认值,因此必须提供值才能匹配。
也可以通过
:defaults
选项定义自己的默认值。例如,使
:isbn
成为可选参数:
map.connect 'library/:action/:isbn', :controller => "library",
:defaults => { :isbn => nil }
如果省略ISBN,它将不会出现在参数哈希中,并且由于移除了这个约束,现在也可以省略动作,使用
:action
的默认值
"index"
。
3. 路由通配符
在路由模式中,可以使用类似Ruby方法中可变参数的方式来捕获URL中可变数量的斜杠分隔令牌。如果在模式中放置
*path
(或其他名称),可以通过
params[:path]
捕获URL中的可变数量的令牌,这称为“路由通配符”。与常规Ruby方法不同,通配符不必是模式中的最后一个参数。
例如,模式为
'library/:action/:isbn/*extra/preview'
,用户请求的路径为
'/library/borrow/9780470189481/3/weeks/preview'
,不仅路径与模式匹配,还可以通过
params[:extra]
访问额外的参数,其值为
["3", "weeks"]
。
4. 路由选项
connect
方法接受第二个参数,即选项哈希,以下是一些常见的选项:
| 选项 | 描述 |
| — | — |
|
:action
| 用于指示当请求的URL与路由声明指定的模式匹配时,应该由哪个动作处理请求。如果模式中存在
:action
,则该值优先于
:action
选项。 |
|
:conditions
| 用于定义路由的限制,支持
:method
条件,指定哪些HTTP方法可以访问该路由,如
:post
、
:get
、
:put
、
:delete
和
:any
。 |
|
:controller
| 用于指示应该将哪个控制器映射到该路由。如果模式中存在
:controller
,则
:controller => "example"
选项仅在URL中未提供控制器时适用。 |
|
:defaults
| 用于为传递给
connect
的模式中包含的一个或多个命名参数指定默认值。 |
|
:<parameter_name>
| 根据分配的值,此选项可用于两个不同的任务。当分配正则表达式时,为命名参数设置要求;当分配常规值时,将参数添加到
params
对象中。 |
|
:requirements
| 用于指定URL中一个或多个参数的格式约束。如果请求中指定的任何参数不满足
:requirements
哈希中定义的条件,则该路由将不适用。 |
5. 路由优先级
routes.rb
文件中定义的路由块可以包含多个路由,可能会有多个路由匹配传入的请求URL和HTTP动词。但每个请求只能映射到一个路由,因此路由系统为每个定义的路由分配了不同的优先级。模式匹配算法从顶部开始向下检查每个路由的模式、条件和要求,直到找到匹配项。找到匹配的路由后,不再检查该请求的其他路由。
这意味着路由块中的路由按优先级顺序排列,第一条路由优先级最高,最后一条路由优先级最低。默认路由通常放在块的末尾,因为它们比较通用,可以在更具体的路由不适用时作为“兜底”路由。如果没有路由匹配传入的请求,将引发
ActionController::RoutingError
异常。
例如,对于简单博客应用的
routes.rb
文件,如果将默认路由放在顶部:
# Don’t do this
ActionController::Routing::Routes.draw do |map|
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
map.root :controller => "articles"
map.resources :articles, :has_many => :comments,
:collection => { :unpublished => :get }
end
当收到RESTful请求
/articles/3/show
时,路由会尝试与第一条路由匹配,最终会将请求错误地映射到动作
3
和
id
为
show
。因此,在RESTful应用中,如果不需要默认路由,建议将其注释掉。
6. 从控制台使用路由
控制台在处理路由时也非常有用,主要有两个任务:确定哪个路由匹配给定的URL,以及根据控制器、动作和参数获取URL。
所有在
routes.rb
中定义的路由都添加到
Routes
对象中,可以通过以下步骤从控制台使用路由:
1. 启动控制台并将
ActionController::Routing::Routes
分配给一个局部变量:
>> routes = ActionController::Routing::Routes
-
如果要测试尚未定义的控制器的路由,可以将它们的名称放在数组中并传递给
use_controllers!:
>> ActionController::Routing::use_controllers! ["main", "library"]
- 重新加载路由文件:
>> load 'config/routes.rb'
-
使用
recognize_path方法识别路径:
>> routes.recognize_path '/main/show/3'
=> {:action => "show", :controller => "main", :id => "3"}
-
使用
generate方法生成URL:
>> routes.generate :controller => 'main', :coupon => 1920321
=> "/main?coupon=1920321"
7. 命名路由
connect
方法生成未命名路由,而
ActionController
提供了为路由定义标签的能力,即命名路由。例如,在
routes.rb
文件中定义:
map.root :controller => "articles"
通过使用
root
而不是
connect
,创建了一个命名路由
root
,可以通过
root_url
(如
http://localhost:3000/
)和
root_path
(如
/
)轻松访问。
命名路由很方便,因为可以使用辅助方法来引用它们,而不必每次都向
url_for
、
redirect_to
或
link_to
等方法指定控制器、动作、默认值等。虽然
root
路由的优势不太明显,但在更复杂的场景中,命名路由的优势会更加突出。
graph LR
A[传入请求] --> B{路由匹配}
B -->|匹配成功| C[执行对应控制器和动作]
B -->|匹配失败| D[抛出RoutingError异常]
综上所述,Rails的路由系统提供了丰富的功能和灵活性,通过合理定义和定制路由,可以更好地处理各种请求。在实际开发中,要注意路由的优先级和参数的使用,以确保请求能够正确映射到相应的控制器和动作。同时,命名路由和控制台的使用也能提高开发效率。
Rails路由系统:定义、定制与使用
8. 命名路由的优势与应用场景
命名路由在实际开发中具有显著的优势,尤其是在处理复杂的路由逻辑时。除了前面提到的
root
路由,我们可以通过更多的例子来展示其强大之处。
假设我们有一个电子商务应用,其中有商品展示、购物车和订单处理等功能。我们可以在
routes.rb
中定义以下命名路由:
map.products :controller => "products", :action => "index"
map.cart :controller => "cart", :action => "show"
map.checkout :controller => "orders", :action => "new"
这样,在视图或控制器中,我们可以使用生成的辅助方法来引用这些路由,而无需每次都手动构建URL。例如:
# 在视图中生成商品列表页面的链接
<%= link_to "商品列表", products_url %>
# 在控制器中重定向到购物车页面
redirect_to cart_path
命名路由的另一个重要应用场景是在国际化应用中。当我们需要支持多种语言时,不同语言的URL可能会有所不同。使用命名路由,我们可以轻松地根据当前语言环境生成正确的URL。例如,在使用
i18n
进行国际化时,我们可以这样定义路由:
map.with_options :path_names => { :new => 'neu', :edit => 'bearbeiten' } do |map|
map.products :controller => "products", :action => "index"
end
这样,在德语环境下,
products_url
生成的URL可能会包含德语的路径名称,而在其他语言环境下则会根据相应的配置生成正确的URL。
9. 路由与RESTful架构的结合
在现代Web开发中,RESTful架构是一种非常流行的设计风格,它强调资源的统一接口和状态转移。Rails的路由系统与RESTful架构紧密结合,提供了便捷的方式来定义RESTful路由。
在
routes.rb
中,我们可以使用
map.resources
方法来定义RESTful资源的路由。例如,对于一个博客应用的文章资源,我们可以这样定义:
map.resources :articles
这将自动生成以下一组RESTful路由:
| HTTP方法 | 路径 | 动作 | 用途 |
| — | — | — | — |
| GET |
/articles
|
index
| 显示所有文章列表 |
| GET |
/articles/new
|
new
| 显示创建新文章的表单 |
| POST |
/articles
|
create
| 创建新文章 |
| GET |
/articles/:id
|
show
| 显示单篇文章 |
| GET |
/articles/:id/edit
|
edit
| 显示编辑文章的表单 |
| PUT |
/articles/:id
|
update
| 更新文章 |
| DELETE |
/articles/:id
|
destroy
| 删除文章 |
这种自动生成的路由使得我们可以很方便地实现RESTful风格的API。同时,我们还可以通过
map.resources
的选项来定制这些路由。例如,如果我们希望文章资源有一个自定义的动作
unpublished
,可以这样定义:
map.resources :articles, :collection => { :unpublished => :get }
这将生成一个新的路由
GET /articles/unpublished
,用于显示未发布的文章列表。
10. 路由的错误处理
在实际应用中,不可避免地会遇到一些无法匹配的路由请求。为了提供更好的用户体验,我们需要对这些错误进行适当的处理。
当没有路由匹配传入的请求时,Rails会抛出
ActionController::RoutingError
异常。我们可以在
ApplicationController
中捕获这个异常,并返回一个自定义的错误页面。例如:
class ApplicationController < ActionController::Base
rescue_from ActionController::RoutingError, with: :render_404
private
def render_404
render file: "#{Rails.root}/public/404.html", status: 404, layout: false
end
end
这样,当用户访问一个不存在的路由时,将显示自定义的404错误页面。
此外,我们还可以通过
map.connect
定义一些特殊的路由来处理常见的错误情况。例如,我们可以定义一个路由来处理所有未匹配的请求:
map.connect '*path', :controller => 'errors', :action => 'not_found'
在
ErrorsController
中,我们可以实现
not_found
动作来处理这些未匹配的请求:
class ErrorsController < ApplicationController
def not_found
render status: 404
end
end
11. 路由的性能优化
随着应用的发展,路由规则可能会变得越来越复杂,这可能会影响路由匹配的性能。为了优化路由性能,我们可以采取以下几个措施:
- 减少路由数量 :尽量避免定义过多的冗余路由。对于一些可以合并的路由,进行合并处理。例如,如果有多个路由的逻辑非常相似,可以考虑使用路由通配符或参数来简化路由定义。
- 合理安排路由顺序 :将最常用的路由放在路由文件的顶部,这样可以减少路由匹配的时间。因为路由匹配算法是从顶部开始向下检查的,越早找到匹配的路由,性能就越好。
-
使用缓存
:Rails的路由系统会对路由信息进行缓存,以提高匹配速度。在生产环境中,确保路由缓存是启用的,可以通过设置
config.action_controller.perform_caching = true来启用缓存。
12. 路由的测试
在开发过程中,对路由进行测试是确保路由功能正确性的重要步骤。Rails提供了方便的测试工具来测试路由。
我们可以使用
ActionController::TestCase
来编写路由测试。例如,对于前面定义的博客应用的文章资源路由,我们可以编写以下测试用例:
require 'test_helper'
class RoutesTest < ActionController::TestCase
test "should route to articles index" do
assert_routing "/articles", { :controller => "articles", :action => "index" }
end
test "should route to show an article" do
assert_routing "/articles/1", { :controller => "articles", :action => "show", :id => "1" }
end
end
通过这些测试用例,我们可以验证路由是否按照预期进行匹配。如果路由发生了变化,测试用例可以帮助我们及时发现问题。
13. 路由系统的工作流程总结
为了更好地理解Rails路由系统的工作原理,我们可以总结其工作流程如下:
graph LR
A[客户端发送请求] --> B{路由匹配开始}
B --> C[从路由文件顶部开始检查路由]
C --> D{是否匹配当前路由}
D -->|是| E[提取参数,执行对应控制器和动作]
D -->|否| F{是否还有下一条路由}
F -->|是| C
F -->|否| G[抛出RoutingError异常]
E --> H[处理请求,生成响应]
H --> I[将响应返回给客户端]
当客户端发送请求时,Rails的路由系统会从
routes.rb
文件的顶部开始,依次检查每条路由的模式、条件和要求,直到找到匹配的路由。如果找到匹配的路由,会提取URL中的参数,并执行对应的控制器和动作。如果没有找到匹配的路由,则会抛出
ActionController::RoutingError
异常。
综上所述,Rails的路由系统是一个功能强大且灵活的工具,它为我们处理请求提供了丰富的功能和便捷的方式。通过合理使用路由选项、命名路由、RESTful路由等特性,以及注意路由的优先级、性能优化和测试等方面,我们可以构建出高效、可维护的Web应用。在实际开发中,深入理解和掌握路由系统的工作原理和使用方法,将有助于我们更好地实现各种复杂的业务逻辑。
超级会员免费看
10

被折叠的 条评论
为什么被折叠?



