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部署与运维

资产与后台任务

Propshaft、Solid Queue 与 Active Storage。

30 分钟

资产与后台任务

这一课你将能做到

  1. 说清楚 Rails 7 资产方案(Propshaft + Importmap)如何免打包加载 JS/CSS
  2. 写一个 Active Job 后台任务并用 Solid Queue 跑起来
  3. 判断什么该进后台队列,并配置失败重试

为什么要学

两件事:第一,Rails 7 彻底告别了 Webpacker 时代——现在默认「不打包」,浏览器原生 ES Module + Importmap 直接加载 JS,这是现代前端观念在 Rails 的落地,和本站 Next.js 的重打包路线形成鲜明对照。第二,任何真实应用都有「不能让用户等」的操作:发邮件、生成报表、同步第三方——这些必须进后台队列,否则一个慢操作拖垮整个 Web 进程。

先建立图像

资产管道(前端)
app/assets/stylesheets ─┐
app/javascript          ├─► Propshaft(指纹+服务)─► 浏览器原生加载
vendor/javascript       ─┘   Importmap 声明依赖映射

后台任务(后端)
Web 进程:  请求进来 → SomeJob.perform_later ─► 队列表(数据库)
                                                   │
Queue 进程:solid_queue 轮询 ◄──────────────────────┘
              │
              ▼
         执行任务 → 成功 / 失败重试

Rails 8/7.2 的默认队列后端 Solid Queue 把任务存在数据库表里,不需要 Redis——中小项目又省一个中间件。

跟着做

步骤 1:理解 Importmap 工作方式

config/importmap.rb:

pin "application"
pin "@hotwired/turbo-rails", to: "turbo.min.js"
pin "@hotwired/stimulus", to: "stimulus.min.js"
pin_all_from "app/javascript/controllers", under: "controllers"

布局里的 <%= javascript_importmap_tags %> 生成:

<script type="importmap">
{"imports": {"application": "/assets/application-abc123.js", ...}}
</script>
<script type="module">import "application"</script>

浏览器拿到 importmap 后按 ES Module 原生机制加载,文件名带内容指纹(abc123)实现永久缓存 + 更新即失效。加新库不用打包:

bin/importmap pin lodash
# importmap.rb 多一行 pin "lodash",JS 里直接 import _ from "lodash"

步骤 2:Stimulus 写一个交互

bin/rails generate stimulus clipboard
// app/javascript/controllers/clipboard_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["source"]

  copy() {
    navigator.clipboard.writeText(this.sourceTarget.value)
  }
}

HTML 里用 data 属性挂接:

<div data-controller="clipboard">
  <input data-clipboard-target="source" value="https://example.com/article/1" readonly>
  <button data-action="clipboard#copy">复制链接</button>
</div>

data-controller 挂载、data-xxx-target 标记元素、data-action 绑定事件——Stimulus 三件套,这是 Rails 全栈处理「小交互」的标准姿势,大交互才考虑前端框架。

步骤 3:CSS 与静态资产

app/assets/stylesheets/application.css 是入口,可用普通 CSS(现代浏览器支持的嵌套、变量都能直接写)。图片放 app/assets/images/,视图里:

<%= image_tag "logo.png", alt: "Logo" %>
<%# 输出带指纹路径:/assets/logo-5d41402abc.png %>

部署前预编译(生产必须):

bin/rails assets:precompile

步骤 4:写一个后台任务

场景:文章发布后异步推送通知。

bin/rails generate job ArticleNotify
# app/jobs/article_notify_job.rb
class ArticleNotifyJob < ApplicationJob
  queue_as :default
  retry_on StandardError, wait: 5.seconds, attempts: 3
  discard_on ActiveRecord::RecordNotFound   # 记录已删,放弃执行

  def perform(article_id)
    article = Article.find(article_id)
    # 调用推送/邮件服务,这里以日志示意
    Rails.logger.info "推送新文章通知:#{article.title}"
  end
end

控制器里入队(非阻塞,立即返回):

def create
  @article = current_user.articles.build(article_params)
  if @article.save
    ArticleNotifyJob.perform_later(@article.id)   # 只传 id,别传对象
    redirect_to @article, notice: "已发布,通知推送中"
  else
    render :new, status: :unprocessable_entity
  end
end

步骤 5:配置并运行队列

config/application.rb(或对应 environment 配置):

config.active_job.queue_adapter = :solid_queue
# 安装队列表并迁移
bin/rails solid_queue:install
bin/rails db:migrate

# 开发环境跑 worker(另开一个终端)
bin/jobs

任务进入数据库的 solid_queue_jobs 表,worker 进程轮询取出执行。验证:创建一个文章,看 worker 终端输出推送日志。

定时任务(如每天清理草稿)用 config/recurring.yml:

production:
  cleanup_drafts:
    command: "DraftCleanupJob.perform_later"
    schedule: every day at 3am

讲清楚要点

  1. Rails 7 资产哲学:No-Build:不写 JSX/TypeScript 就不需要打包器。Propshaft 负责指纹和服务静态文件,Importmap 负责模块映射,浏览器干剩下的。需要 npm 重生态时再选 esbuild/jsbundling-rails。
  2. perform_later 只传简单参数:传 article.id 不传 article 对象——任务执行可能在几分钟后,期间对象可能已删或已改,队列里序列化的是参数快照。GlobalID 虽支持传模型,但最佳实践仍是传 id 重新查。
  3. 任务要幂等:同一任务执行两次不应出错(重试机制可能重复执行)。用唯一约束、find_or_create_by、状态检查来保证。
  4. 失败策略显式声明:retry_on 处理瞬时错误(网络抖动),discard_on 处理永久错误(记录已删)。不声明则默认失败进死信。
  5. Solid Queue 让队列零依赖:任务存数据库,省去 Redis/RabbitMQ。QPS 极高的场景再考虑 Sidekiq + Redis。

你可能会懵的地方

  • Importmap 下能用 React 吗? 技术上可以 pin React 并用 htm 写组件,但很痛苦。要用 React/Vue 认真做前端,就引入 jsbundling-rails + esbuild,或者干脆前后端分离(本站路线)。Importmap 的主场是 Stimulus 级交互。

  • perform_now 和 perform_later 区别? perform_now 同步立即执行(测试和调试用);perform_later 入队异步执行。还有 perform_later(wait: 10.minutes) 延迟执行。

  • 开发环境任务不执行? 检查队列适配器:开发默认是 :async(进程内线程,重启即丢)。想用真实流程就配 :solid_queue 并跑 bin/jobs;只想看日志用 :async 也行。

  • 多服务器部署时任务会重复执行吗? 不会。Solid Queue 用数据库行锁保证一条任务只被一个 worker 取走。这也是「数据库即队列」方案天然的优势。

  • 邮件也是任务吗? 是。ArticleMailer.notify(article).deliver_later 自动包成 Active Job 入队。邮件、Active Storage 图片处理、Action Cable 广播底层都走队列。

错法对照

错法问题正解
控制器里同步发邮件/调第三方慢操作阻塞请求,拖垮 Pumadeliver_later / Job 入队
Job.perform_later(@article) 传对象执行时对象可能已删/数据过期传 id,任务里重新 find
任务非幂等(如直接 increment 计数)重试导致重复计数幂等设计或数据库唯一约束
不声明 retry/discard 策略瞬时故障任务直接死亡retry_on + discard_on 组合
生产忘了跑 worker 进程任务堆积无人执行部署脚本包含 bin/jobs 进程管理

卡点急救

  1. 改了 JS 浏览器没变化:指纹没变说明文件没改对位置;确认在 app/javascript/ 下且 importmap 有对应 pin;强刷(Ctrl+F5)。
  2. 任务入队了但 worker 无输出:worker 进程没启动(bin/jobs);或队列名不匹配——queue_as :default 而 worker 只听别的队列。
  3. 生产报 Missing host to link to(邮件里生成 URL):配 config.action_mailer.default_url_options = { host: "example.com" },任务里生成完整 URL 同理需要 host。

面试常问

  • Rails 7 为什么放弃 Webpacker? 答:浏览器原生支持 ES Module 后,「不打包」成为可能:文件即模块、指纹即缓存策略,开发零编译、刷新即生效。Webpack 生态对 Rails 全栈场景过重——配置复杂、构建慢。Rails 的选择是把简单留给多数人(Importmap + Stimulus),把复杂留给真需要的人(jsbundling/cssbundling 可选接入)。

  • 后台任务为什么要求幂等?怎么实现? 答:网络重试、worker 崩溃重启都可能导致同一任务执行多次。实现手段:数据库唯一约束(重复插入直接失败)、find_or_create_by、先查状态再操作(return if article.notified?)、外部 API 用幂等键。原则是「执行 N 次 = 执行 1 次的效果」。

  • Solid Queue 和 Sidekiq 怎么选? 答:Solid Queue 用数据库当队列,零额外中间件、事务一致性(任务和业务数据同库提交)、部署简单,中小规模首选;Sidekiq + Redis 吞吐更高(内存操作)、生态成熟,日均千万级任务或已有 Redis 基础设施时用。Rails 8 默认 Solid Queue,代表官方对「够用且简单」的背书。

自测三问

  1. Importmap 模式下浏览器如何找到 application.js?指纹解决什么问题?
  2. 为什么 perform_later 推荐传 id 而不是模型对象?
  3. Solid Queue 相比 Sidekiq 省掉了什么?代价是什么?

小结一句话

资产走 No-Build 哲学(Importmap + Propshaft),慢操作走后台队列(Active Job + Solid Queue),任务参数传 id、执行要幂等。

延伸

  • Active Job Basics(官方后台任务指南)
  • 下一课:测试与调试——Minitest、Fixtures 与调试工具箱。
加载本节练习…
← 上一课认证与授权下一课 →测试与调试