CodeRoad Map 程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。
由 程序员柏风 独立维护 · 关于作者
学习 更多
路由设计
这一课你将能做到
用 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:限制只暴露需要的动作 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 带 :id
collection:作用于整个集合,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 %>
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 让全站链接可重构。
延伸