CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Ruby on Rails 实战入门/课时/控制器
RawBlame
课时目录12 节

Ruby on Rails 实战入门

  1. 01Rails 概览与项目结构
  2. 02路由设计
  3. 03控制器
  4. 04Active Record 模型
  5. 05查询与优化
  6. 06数据库适配
  7. 07视图与组件
  8. 08表单
  9. 09认证与授权
  10. 10资产与后台任务
  11. 11测试与调试
  12. 12部署与运维

控制器

动作、参数白名单与回调。

30 分钟

控制器

这一课你将能做到

  1. 写出符合 RESTful 惯例的七个控制器动作,并正确处理成功/失败两条路径
  2. 用 Strong Parameters 挡住批量赋值攻击,说清楚 require 和 permit 的分工
  3. 用 before_action 回调消除重复代码,实现登录校验和数据预加载

为什么要学

控制器是 MVC 里的「交通警察」:接住请求、调取数据、决定渲染哪个视图或跳转到哪里。它的质量直接决定应用的边界清晰度——控制器里混入业务逻辑,三个月后就是一团谁都改不动的乱麻;控制器保持「薄」,业务规则全部在模型层,代码才能长期演进。

对比本站:Spring Boot 的 @RestController 通常直接返回 JSON 给 Next.js 前端;Rails 控制器则同时承担「渲染 HTML 页面」和「处理表单提交后重定向」的职责,respond_to 还能让同一个动作按请求格式返回 HTML 或 JSON——一个控制器服务两种客户端。

先建立图像

一个标准 create 动作的「双路径」心智模型:

表单提交 POST /articles
        │
        ▼
  @article = Article.new(article_params)
        │
        ▼
   @article.save ?
    ╱          ╲
  成功          失败
   │              │
   ▼              ▼
redirect_to    render :new,
@article       status: :unprocessable_entity
(看详情页)      (回到表单页显示错误)

记住这张图,90% 的 CRUD 控制器都是它的变体。

跟着做

步骤 1:生成并通读控制器

bin/rails generate controller Articles

app/controllers/articles_controller.rb:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)
    if @article.save
      redirect_to @article, notice: "文章创建成功"
    else
      render :new, status: :unprocessable_entity
    end
  end

  def edit
    @article = Article.find(params[:id])
  end

  def update
    @article = Article.find(params[:id])
    if @article.update(article_params)
      redirect_to @article, notice: "文章已更新"
    else
      render :edit, status: :unprocessable_entity
    end
  end

  def destroy
    @article = Article.find(params[:id])
    @article.destroy
    redirect_to articles_path, notice: "文章已删除", status: :see_other
  end

  private

  def article_params
    params.require(:article).permit(:title, :body)
  end
end

步骤 2:理解 Strong Parameters

表单提交的数据长这样:

params = {
  "authenticity_token" => "...",
  "article" => { "title" => "标题", "body" => "正文", "admin" => "true" },
  "commit" => "保存"
}

注意攻击者多塞了一个 字段。如果没有白名单, 会把 一起写库——这就是「批量赋值漏洞」(Mass Assignment)。

admin
Article.new(params[:article])
admin: true

article_params 方法做了两道过滤:

params.require(:article)           # 必须有 :article 这个键,否则抛 400 异常
       .permit(:title, :body)      # 只放行 title 和 body,admin 被静默丢弃

返回的是一个「已许可」的 ActionController::Parameters 对象,才能安全传给模型。

步骤 3:用 before_action 消除重复

show/edit/update/destroy 都在重复 Article.find(params[:id]),用回调收敛:

class ArticlesController < ApplicationController
  before_action :set_article, only: [:show, :edit, :update, :destroy]

  def show
  end

  def edit
  end

  def update
    if @article.update(article_params)
      redirect_to @article, notice: "文章已更新"
    else
      render :edit, status: :unprocessable_entity
    end
  end

  def destroy
    @article.destroy
    redirect_to articles_path, notice: "文章已删除", status: :see_other
  end

  private

  def set_article
    @article = Article.find(params[:id])
  end

  def article_params
    params.require(:article).permit(:title, :body)
  end
end

show 和 edit 方法体直接空了——回调已经备好数据,Rails 按约定渲染同名视图。

步骤 4:一个动作同时服务 HTML 和 JSON

def index
  @articles = Article.all
  respond_to do |format|
    format.html                          # 渲染 index.html.erb
    format.json { render json: @articles }  # /articles.json 返回 JSON
  end
end

验证:

curl http://localhost:3000/articles.json

这就是 Rails 全栈模式的弹性:同一套控制器既能渲染页面,也能当 API 用。当然,纯 API 项目可以用 rails new myapp --api 生成精简版控制器基类。

步骤 5:flash 提示与状态码

redirect_to @article, notice: "保存成功"    # 一次性的成功提示
redirect_to login_path, alert: "请先登录"   # 一次性的警告提示
render :new, status: :unprocessable_entity  # 422:表单校验失败
redirect_to articles_path, status: :see_other  # 303:DELETE 后正确重定向

flash 消息只存活一次请求,显示在布局里:

<%# app/views/layouts/application.html.erb 的 body 内 %>
<% flash.each do |type, message| %>
  <div class="flash flash-<%= type %>"><%= message %></div>
<% end %>

讲清楚要点

  1. 控制器要「薄」:动作里只做「取参数 → 调模型 → 决定响应」三件事。超过 10 行的动作就该考虑把逻辑下沉到模型或 Service 对象。
  2. Strong Parameters 是强制安全门:Rails 4 起强制要求,不过滤 params 直接传模型会直接抛 ForbiddenAttributesError,从框架层面堵死批量赋值漏洞。
  3. 回调三兄弟:before_action(动作前,常用于认证和加载数据)、after_action(动作后,较少用)、around_action(包裹动作,可做耗时统计)。都支持 only/except。
  4. render 与 redirect_to 的本质区别:render 在同一请求内直接渲染模板(URL 不变,可带实例变量);redirect_to 让浏览器发起新请求(URL 变化,实例变量丢失,靠 flash 传消息)。
  5. 表单校验失败要 render 不要 redirect:失败时 render :new 能保留 @article.errors 和用户已填内容;redirect 会新开请求,错误信息和表单内容全丢。记得带 status: :unprocessable_entity,否则 Turbo 下表单不工作。

你可能会懵的地方

  • params 里的数据从哪来的? 三个来源合并:URL 查询串(?page=2)、表单 POST 体、路由路径段(:id)。统一用 params[:key] 访问,不用关心来源。

  • 为什么失败时状态码用 422 而不是 200? HTTP 语义上 422(Unprocessable Entity)表示「请求格式正确但内容校验失败」。Turbo/Hotwire 依赖非 2xx 状态码来判断「该替换表单而不是跳转」,这是 Rails 7 的重要约定。

  • set_article 为什么要 private? public 方法可能被当成动作(action)响应请求。回调方法、参数过滤方法都必须放 private 下面。

  • 继承的 ApplicationController 是干什么的? 所有控制器的公共基类,放全局回调(如登录校验)、异常救援(rescue_from)、helper 声明。它继承 ActionController::Base。

  • 控制器里能直接查数据库吗? 语法上可以,但这是典型的「胖控制器」坏味道。查询逻辑应封装在模型的 scope 或类方法里,控制器只调用语义化的接口。

错法对照

错法问题正解
Article.new(params[:article]) 不过滤批量赋值漏洞,攻击者可改任意字段params.require(:article).permit(...)
四个动作各写一遍 Article.find重复代码,改逻辑要改四处before_action :set_article
校验失败后 redirect_to new_article_path错误信息和已填内容全丢render :new, status: :unprocessable_entity
回调方法写成 public可能被当动作直接访问放 private 之下
控制器里堆 50 行业务逻辑无法复用、难以测试下沉到模型或 Service 对象

卡点急救

  1. 报 ActionController::ParameterMissing:表单字段名没包在 article[...] 里。检查表单是否用了 form_with model: @article,它会自动生成正确的字段名。
  2. 报 ForbiddenAttributesError:直接把整个 params 传给了模型,改用 Strong Parameters 过滤后的结果。
  3. Turbo 下表单提交后页面不更新也不报错:失败分支少了 status: :unprocessable_entity,补上即可。

面试常问

  • 什么是批量赋值漏洞?Rails 怎么防? 答:攻击者在表单数据里注入额外字段(如 admin=true),若服务端把整个参数哈希直接传给模型,恶意字段就会被写库。Rails 4 起强制 Strong Parameters:必须显式 require 顶层键、permit 白名单字段,未过滤的参数传给模型直接抛异常,框架层面杜绝该漏洞。

  • render 和 redirect_to 的区别?什么场景用哪个? 答:render 在当前请求内渲染模板,URL 不变、实例变量保留,用于「展示响应」;redirect_to 返回 3xx 让浏览器发新请求,用于「操作成功后跳转」防止刷新重复提交(PRG 模式)。表单校验失败必须 render,成功必须 redirect。

  • before_action 的常见用途?要注意什么坑? 答:认证(require_login)、授权(检查资源归属)、数据预加载(set_article)。坑:回调里没 halt 链——before_action 里 redirect 或 render 后动作就不再执行,但若只设置实例变量不中断,动作照常跑,容易出意外行为。

自测三问

  1. create 动作的成功和失败两条路径分别用什么响应方式?为什么失败不能用 redirect?
  2. params.require(:article).permit(:title, :body) 中 require 和 permit 各自挡住什么?
  3. before_action 里写了 redirect_to login_path,动作方法还会执行吗?

小结一句话

控制器的本分是「薄」:接参数、调模型、选响应,用 Strong Parameters 守安全门,用 before_action 去重复。

延伸

  • Action Controller Overview(官方控制器指南)
  • 下一课:Active Record 模型——业务规则的真正居所,验证、回调与迁移。
加载本节练习…
← 上一课路由设计下一课 →Active Record 模型