大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
路由设计
这一课你将能做到
- 用
resources一行声明完整的 RESTful 七路由,并用bin/rails routes验证 - 设计嵌套路由表达「评论属于文章」这类从属关系
- 用命名路由 helper(
article_path)在代码里生成 URL,而不是手写字符串
为什么要学
路由是应用的「门面」:URL 设计得好,用户能猜到地址、搜索引擎友好、团队协作时接口语义清晰。Rails 的路由不只是 URL 映射表,它还反向生成 URL helper——你在视图里写 article_path(@article),重构 URL 结构时只改 routes.rb 一处,全站链接自动跟随。
对比本站:Spring Boot 里路由散落在每个 @GetMapping("/articles/{id}") 注解上;Rails 则集中在 config/routes.rb 一个文件里,整张路由表一目了然。两种风格各有拥趸,但集中式的可审计性在大项目里优势明显。
先建立图像
resources :articles 一行展开成的七条标准路由:
| HTTP 动词 | 路径 | 控制器动作 | 用途 | 命名 helper |
|---|---|---|---|---|
| GET | /articles | index | 列表页 | articles_path |
| GET | /articles/new | new | 新建表单页 | new_article_path |
| POST | /articles | create | 处理创建提交 | articles_path |
| GET | /articles/:id | show | 详情页 | article_path(id) |
| GET | /articles/:id/edit | edit | 编辑表单页 | edit_article_path(id) |
| PATCH/PUT | /articles/:id | update | 处理更新提交 | article_path(id) |
| DELETE | /articles/:id | destroy | 删除 | article_path(id) |
请求匹配流程:
GET /articles/5
│
▼
routes.rb 自上而下逐条匹配
│
▼
命中 get "/articles/:id", to: "articles#show"
│
▼
params[:id] = "5" ──► ArticlesController#show
跟着做
步骤 1:声明资源路由并查看路由表
编辑 config/routes.rb:
Rails.application.routes.draw do
root "articles#index" # 首页
resources :articles
end
查看生成的路由表:
bin/rails routes
输出(节选):
Prefix Verb URI Pattern Controller#Action
root GET / articles#index
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
new_article GET /articles/new(.:format) articles#new
article GET /articles/:id(.:format) articles#show
PATCH /articles/:id(.:format) articles#update
DELETE /articles/:id(.:format) articles#destroy
步骤 2:限制只暴露需要的动作
如果文章不允许删除,用 except 收窄:
resources :articles, except: [:destroy]
或者只开放读接口:
resources :articles, only: [:index, :show]
原则:只暴露用到的路由,路由表就是你的攻击面清单。
步骤 3:嵌套路由表达从属关系
评论必须属于某篇文章:
Rails.application.routes.draw do
root "articles#index"
resources :articles do
resources :comments, only: [:create, :destroy]
end
end
生成的路径形如 /articles/5/comments,控制器里 params[:article_id] 自动可用:
# app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def create
@article = Article.find(params[:article_id])
@comment = @article.comments.create(comment_params)
redirect_to article_path(@article)
end
private
def comment_params
params.require(:comment).permit(:commenter, :body)
end
end
嵌套不要超过一层。两层以上(/a/1/b/2/c/3)URL 和控制器都会变得难维护,这时候用浅层嵌套(shallow: true)或扁平化设计。
步骤 4:自定义成员路由与集合路由
业务里常有超出标准七动作的需求,比如「发布文章」:
resources :articles do
member do
patch :publish # PATCH /articles/:id/publish
end
collection do
get :archived # GET /articles/archived
end
end
member:作用于单条记录,URL 带:idcollection:作用于整个集合,URL 不带 id
步骤 5:在视图和控制台里使用命名 helper
<%# app/views/articles/index.html.erb %>
<%= link_to "写文章", new_article_path %>
<%= link_to article.title, article_path(article) %>
<%= button_to "删除", article_path(article), method: :delete %>
在 bin/rails console 里验证:
app.article_path(3) # => "/articles/3"
app.articles_url # => "http://www.example.com/articles"
app.publish_article_path(7) # => "/articles/7/publish"
_path 生成相对路径,_url 生成带域名的完整 URL(邮件里必须用 _url)。
讲清楚要点
- RESTful 是资源思维:URL 描述「资源」(名词),HTTP 动词描述「操作」。
/articles/5+ DELETE 就是删除 5 号文章,不需要/articles/delete/5这种把动词塞进 URL 的写法。 - routes.rb 自上而下匹配,先命中先生效:通配路由(
get "*path")必须放最后,否则会拦截后面的具体路由。 :id段进 params:路径里的:id、:article_id占位符都会进入params哈希,控制器直接取用。- 命名 helper 是路由的反向收益:永远不要手写
"/articles/#{id}",用article_path(id),URL 结构变更时代码零改动。 - 重定向与约束:
get "/old", to: redirect("/new")处理老链接;constraints可按 IP、子域名限制路由,后台管理路由常用。
你可能会懵的地方
-
resources和resource(单数)区别? 单数resource :profile表示「用户只有一个资料」,路径是/profile(无 id),helper 是profile_path。适合「当前用户的设置」这类场景。 -
PATCH 和 PUT 都指向 update,用哪个? 语义上 PATCH 是部分更新、PUT 是整体替换,Rails 里二者完全等价地路由到
update动作,实际开发用 PATCH 即可。 -
HTML 表单只支持 GET/POST,DELETE 请求怎么发的? Rails 通过隐藏字段
_method=delete+ JavaScript(data-turbo-method)模拟,服务端 Rack 中间件把它还原成真正的 DELETE 语义。 -
.:format是什么? 路径后缀格式,如/articles.json。控制器里用respond_to按格式返回 HTML 或 JSON,是 Rails 一套代码同时服务页面和 API 的经典手法。 -
路由文件太大怎么办? 用
draw :admin把路由拆到config/routes/admin.rb,Rails 6.1+ 原生支持。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
get "/articles/delete/:id" | 动词塞进 URL,GET 改变数据,违反 REST 与安全语义 | resources :articles,用 DELETE 动词 |
视图里手写 href="/articles/#{a.id}" | URL 变更时全站改 | article_path(a) |
三层嵌套 /a/1/b/2/c/3 | URL 丑陋,控制器要找三个父对象 | 最多嵌一层,或用 shallow: true |
| 通配路由放文件顶部 | 拦截后续所有具体路由 | get "*path" 永远放最后 |
每个动作都手写 get/post | 路由表冗长易漏 | resources + only/except |
卡点急救
- 访问页面报
Routing Error: No route matches:跑bin/rails routes确认路径和动词;在报错页直接用浏览器顶部搜索框过滤路由。 undefined method 'xxx_path':helper 名由Prefix列决定,bin/rails routes | grep xxx查正确前缀;注意单复数。- 嵌套路由里
params[:article_id]是 nil:检查 routes.rb 里resources :comments是否真正写在resources :articles do ... end块内。
面试常问
-
RESTful 路由的七个标准动作是什么? 答:index(列表)、show(详情)、new(新建表单)、create(创建)、edit(编辑表单)、update(更新)、destroy(删除)。对应 GET/POST/PATCH/DELETE 四个动词,URL 只含名词。
-
嵌套路由什么时候用,有什么限制? 答:当子资源离开父资源没有独立意义时用(评论离不开文章)。限制:最多嵌一层,否则 URL 冗长、控制器链式查找父对象成本高;深层关系用浅层嵌套或在子资源路由里传父 id 参数。
-
Rails 路由相比注解式路由(Spring 的 @RequestMapping)的优劣? 答:集中式(routes.rb)优点是全局可审计、
rails routes一条命令看全貌、反向生成 helper;缺点是路由与控制器物理分离,新人要跳两个文件。注解式优点是路由贴着代码、直观;缺点是分散在几十个类里,全局接口清单需要工具扫描才能拿到。
自测三问
resources :articles, only: [:index, :show]会生成哪几条路由?各自的 helper 名是什么?- 为什么
/articles/archived要用collection而不是member? - 在视图中生成文章详情链接,正确写法是什么?为什么不能手写字符串?
小结一句话
路由是 URL 与控制器之间的契约:resources 给你 RESTful 标准答案,命名 helper 让全站链接可重构。
延伸
- Rails Routing from the Outside In(官方路由指南)
- 下一课:控制器——接住路由分发来的请求,协调模型与视图。