Ruby on Rails 8.1 发布说明详解:Active Job Continuations、结构化事件上报与本地 CI 等核心特性
本文基于 Rails 8.1 官方发布说明(guides/source/8_1_release_notes.md),完整覆盖该版本的七大重点特性——Active Job 断点续跑、结构化事件上报、本地 CI、Markdown 渲染、命令行凭据获取、废弃关联报告与无注册表 Kamal 部署,并逐一解读 Railties、Action Pack、Active Record、Active Support、Active Job 等框架的移除、废弃与重要变更,适合正在评估或执行 8.0 到 8.1 升级的 Rails 开发者。
升级到 Rails 8.1 前的准备
官方文档给出的升级路径建议:
- 升级前先保证良好的测试覆盖率;
- 如果应用还没有升级,应先升级到 Rails 8.0,确认应用行为正常后再尝试 8.1;
- 升级时需要留意的事项,汇总在升级指南中。
以下章节按“重大特性”与“各框架变更”两条主线展开,特性部分会结合当前仓库中的源码实现(Active Job、Active Support、Railties、Active Record)做纵深解读。
重大特性一:Active Job Continuations(作业断点续跑)
使用方式
长耗时作业现在可以被拆分为离散的步骤(step),在中断或重启后从最后完成的步骤继续执行,而不是从头重跑。这一点在使用 Kamal 部署时尤其有价值——Kamal 默认只给运行作业的容器 30 秒来关闭。
文档给出的标准示例:
class ProcessImportJob < ApplicationJob
include ActiveJob::Continuable
def perform(import_id)
@import = Import.find(import_id)
# block format
step :initialize do
@import.initialize
end
# step with cursor, the cursor is saved when the job is interrupted
step :process do |step|
@import.records.find_each(start: step.cursor) do |record|
record.process
step.advance! from: record.id
end
end
# method format
step :finalize
end
private
def finalize
@import.finalize
end
end
step 支持 block 与方法名两种形式;带游标(cursor)的步骤在中断时会保存游标,恢复时从游标位置继续。
源码层面的实现细节
结合 activejob/lib/active_job/continuable.rb 与 activejob/lib/active_job/continuation.rb 的实现,可以补充几个文档示例之外的关键机制:
配置项(class_attribute),在 Continuable 模块中定义:
max_resumptions:最多恢复执行的次数,默认nil(不限);超过上限时抛出Continuation::ResumeLimitError;resume_options:恢复时传给retry_job的选项,默认{ wait: 5.seconds };resume_errors_after_advancing:推进过进度后再发生错误是否自动重试,默认true。
class ProcessImportJob < ApplicationJob
include ActiveJob::Continuable
self.max_resumptions = 3
self.resume_options = { wait: 1.seconds, queue: :resumed }
self.resume_errors_after_advancing = false
end
检查点(checkpoint)机制:设置或推进游标(set!、advance!)、显式调用 checkpoint! 都会产生一个检查点;每个步骤开始前(首个步骤除外)也有自动检查点。在检查点处,作业会调用 queue_adapter.stopping?,若返回真值则抛出 Continuation::Interrupt(注意它继承自 Exception 而非 StandardError,因此不会被常规异常处理捕获),由 Active Job 将带进度序列化的作业重新入队。进度数据写入 job data 的 continuation 键,包含已完成步骤列表和当前步骤的游标值(见 Continuation#to_h)。
步骤语义:步骤按序执行、遇到即运行;已完成步骤在恢复时直接跳过(发出 step_skipped 事件);不在步骤内的代码每次执行都会运行。对无法在关停宽限期内设置检查点的超长步骤,可以声明 isolated: true,强制该步骤独占一次执行、开始前先序列化进度。
错误处理:作业在已推进进度(完成过一个步骤或推进过游标)之后抛出错误时,会被自动重试;相关行为可在 activejob/test/cases/continuation_test.rb 与 activejob/test/cases/structured_event_subscriber_test.rb 等测试中验证。
重大特性二:Structured Event Reporting(结构化事件上报)
使用方式
Rails 默认 Logger 面向人类阅读,不利于程序化后处理。新的 Event Reporter 提供了产生结构化事件的统一接口:
Rails.event.notify("user.signup", user_id: 123, email: "user@example.com")
支持给事件打标签(tags):
Rails.event.tagged("graphql") do
# Event includes tags: { graphql: true }
Rails.event.notify("user.signup", user_id: 123, email: "user@example.com")
end
以及设置上下文(context):
# All events will contain context: {request_id: "abc123", shop_id: 456}
Rails.event.set_context(request_id: "abc123", shop_id: 456)
事件会分发给订阅者(subscriber)。应用通过注册订阅者来控制事件如何被序列化与输出;订阅者必须实现接收事件哈希的 #emit 方法:
class LogSubscriber
def emit(event)
payload = event[:payload].map { |key, value| "#{key}=#{value}" }.join(" ")
source_location = event[:source_location]
log = "[#{event[:name]}] #{payload} at #{source_location[:filepath]}:#{source_location[:lineno]}"
Rails.logger.info(log)
end
end
源码层面的事件结构与能力
实现位于 activesupport/lib/active_support/event_reporter.rb。从源码看,notify 产出的事件哈希包含以下键:
name:事件名(字符串或 symbol 会被转为字符串;若传入事件对象,则用其类名);payload:payload 哈希或事件对象本身;tags:由tagged块内累积的标签,基于Fiber存储,可嵌套;context:由set_context设置的请求/作业级上下文,默认存储在EventContext中,可通过config.active_support.event_reporter_context_store换成自定义存储;timestamp:纳秒级时间戳;source_location:调用点信息,包含filepath、lineno与label。
此外源码中还确认了几个实用细节:
- 过滤订阅:
subscribe可传入 filter 过程,让订阅者只接收部分事件,但过滤器只能访问:name键; - debug 模式:
Rails.event.debug仅在 debug 模式下发出事件,with_debug块可临时开启; - 敏感数据过滤:基于 payload 的哈希会按
Rails.application.filter_parameters自动脱敏;若传入事件对象,则需要订阅者自行过滤。
重大特性三:Local CI(本地 CI)
动机与默认 DSL
开发者机器的核心数与速度已经大幅提升,足以本地运行相当规模的测试套件。这使得许多中小型应用完全有理由抛弃云端 CI。为此 Rails 新增了默认 CI 声明 DSL,定义在 config/ci.rb 中,由 bin/ci 执行。文档给出的完整示例:
CI.run do
step "Setup", "bin/setup --skip-server"
step "Style: Ruby", "bin/rubocop"
step "Security: Gem audit", "bin/bundler-audit"
step "Security: Importmap vulnerability audit", "bin/importmap audit"
step "Security: Brakeman code analysis", "bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error"
step "Tests: Rails", "bin/rails test"
step "Tests: Seeds", "env RAILS_ENV=test bin/rails db:seed:replant"
# Requires the `gh` CLI and `gh extension install basecamp/gh-signoff`.
if success?
step "Signoff: All systems go. Ready for merge and deploy.", "gh signoff"
else
failure "Signoff: CI failed. Do not merge or deploy.", "Fix the issues and try again."
end
end
可选的 gh 集成确保 PR 必须被一次通过的 CI 运行“签收”(signoff)后才有资格合并,把“本地验证通过”变成了合并的前置条件。
仓库中的实际落点
当前仓库的 app 生成器会为新应用生成这套文件:模板 railties/lib/rails/generators/rails/app/templates/config/ci.rb.tt 开头即注释 # Run using bin/ci 并包含 CI.run do 块;railties/lib/rails/generators/rails/app/templates/bin/ci.tt 则是一行 require_relative "../config/ci.rb" 的可执行入口。测试 railties/test/application/bin_ci_test.rb 会断言生成的 bin/ci 存在且可执行,因此新建 Rails 应用天然带有一套可运行的本地 CI 声明。
重大特性四:Markdown Rendering(Markdown 渲染)
随着 AI 生态的普及,Markdown 正在成为通用交换格式。Rails 顺应这一趋势,让控制器更轻松地响应并直接渲染 markdown 请求。文档示例:
class Page
def to_markdown
body
end
end
class PagesController < ActionController::Base
def show
@page = Page.find(params[:id])
respond_to do |format|
format.html
format.md { render markdown: @page }
end
end
end
即模型实现 to_markdown、控制器在 respond_to 中声明 format.md 并用 render markdown: ... 渲染,即可让 Accept: text/markdown 之类的请求直接拿到 markdown 内容,无需手写响应体。
重大特性五:Command-line Credentials Fetching(命令行获取凭据)
Kamal 现在可以直接从 Rails 加密的 credentials 存储中提取部署所需的机密,作为外部 Secret Store 的“低保真”替代——前提是 master key 对部署环境可用:
# .kamal/secrets
KAMAL_REGISTRY_PASSWORD=$(rails credentials:fetch kamal.registry_password)
对应的实现位于 railties/lib/rails/commands/credentials/credentials_command.rb,其中 CredentialsCommand#fetch(path)(约 L61)负责按点分路径(如 kamal.registry_password)读取加密存储中的单个值并输出到 stdout,正是上面 shell 命令替换所调用的入口。这意味着部署脚本无需维护独立的 secret 文件,只需把机密写进 config/credentials.yml.enc 再引用即可。
重大特性六:Deprecated Associations(废弃关联报告)
Active Record 关联现在可以显式标记为废弃:
class Author < ApplicationRecord
has_many :posts, deprecated: true
end
标记之后,任何对该关联的使用都会被报告。覆盖范围既包括显式 API 调用:
author.posts
author.posts = ...
也包括间接使用,例如:
author.preload(:posts)
嵌套属性(nested attributes)引发的使用等同样在列。
支持的报告模式有三种::warn、:raise、:notify;backtrace 可以开启或关闭,但无论哪种设置都会附带报告发生位置。默认是 :warn 模式且不输出 backtrace。
从源码看,入口在 activerecord/lib/active_record/associations.rb 的 deprecated_associations_api_guard(L92)与 report_deprecated_association(L96);各关联构建器(如 activerecord/lib/active_record/associations/builder/collection_association.rb、activerecord/lib/active_record/associations/builder/singular_association.rb)中的读取/赋值方法都会先经过该守卫。deprecated 也是 has_many 等关联构建器正式接受的选项之一(见 activerecord/lib/active_record/associations/builder/association.rb 的选项白名单)。这为“关联下线”提供了一种灰度路径:先报告、再决定收紧。
重大特性七:Registry-Free Kamal Deployments(免远程注册表部署)
Kamal 做基础部署不再强依赖 Docker Hub、GHCR 之类的远程镜像注册表:Kamal 2.8 起默认使用本地注册表完成简单部署。大规模部署仍建议使用远程注册表,但这一变化显著降低了上手门槛,让第一次部署 Hello World 应用成为一件更顺畅的事。
各框架变更清单
以下各节完整继承发布说明中按框架划分的 Removals / Deprecations / Notable changes,详细变更可分别参阅 railties/CHANGELOG.md、actionpack/CHANGELOG.md、actionview/CHANGELOG.md、actionmailer/CHANGELOG.md、actioncable/CHANGELOG.md、activerecord/CHANGELOG.md、activestorage/CHANGELOG.md、[activemodel/CHANGELOG.md)(activemodel/CHANGELOG.md)、activesupport/CHANGELOG.md、activejob/CHANGELOG.md、actiontext/CHANGELOG.md 与各框架 CHANGELOG。
Railties
移除(Removals):
- 移除已废弃的
rails/console/methods.rb文件; - 移除已废弃的
bin/rake stats命令; - 移除已废弃的
STATS_DIRECTORIES。
Action Pack
移除(Removals):
-
移除参数解析器中“跳过参数名开头方括号”的废弃支持。行为变化对比:
之前:
ActionDispatch::ParamBuilder.from_query_string("[foo]=bar") # => { "foo" => "bar" } ActionDispatch::ParamBuilder.from_query_string("[foo][bar]=baz") # => { "foo" => { "bar" => "baz" } }之后:
ActionDispatch::ParamBuilder.from_query_string("[foo]=bar") # => { "[foo]" => "bar" } ActionDispatch::ParamBuilder.from_query_string("[foo][bar]=baz") # => { "[foo]" => { "bar" => "baz" } }即前导
[不再被剥离,而是作为参数名的一部分保留——依赖旧行为的参数解析代码会受影响。 -
移除以分号作为查询串分隔符的废弃支持。行为对比:
之前:
ActionDispatch::QueryParser.each_pair("foo=bar;baz=quux").to_a # => [["foo", "bar"], ["baz", "quux"]]之后:
ActionDispatch::QueryParser.each_pair("foo=bar;baz=quux").to_a # => [["foo", "bar;baz=quux"]]分号从此是普通字符,只有
&作为分隔符。 -
移除路由指向多个路径(a route to multiple paths)的废弃支持。
废弃(Deprecations):
- 废弃
Rails.application.config.action_dispatch.ignore_leading_brackets配置项(与上面第一项移除对应)。
重要变更(Notable changes):
-
在新建的 Rails 应用中,重定向(redirect)在 development 环境下会输出更详细的日志。存量应用如需开启,在
config/development.rb中加入:config.action_dispatch.verbose_redirect_logs = true
Action View
废弃(Deprecations):
- 废弃携带无关键字参数的
:renderable对象(即响应#render_in的对象)的render调用方式。
Active Record
移除(Removals):
- 移除 SQLite3 适配器已废弃的
:retries选项; - 移除 MySQL 已废弃的
:unsigned_float与:unsigned_decimal列方法。
废弃(Deprecations):
- 废弃在不指定
order的情况下使用依赖顺序的查找方法(如#first); - 废弃
ActiveRecord::Base.signed_id_verifier_secret,改用Rails.application.message_verifiers(若密钥是特定于某模型的,用Model.signed_id_verifier); - 废弃在关联中使用未持久化记录执行
insert_all/upsert_all; - 废弃在
update_all中使用WITH、WITH RECURSIVE与DISTINCT。
重要变更(Notable changes):
schema.rb中的表列现在按字母顺序排序。
Active Storage
移除(Removals):
- 移除已废弃的
:azure存储服务。
Active Support
移除(Removals):
- 移除向
Time#since传入 Time 对象的废弃用法; - 移除已废弃的
Benchmark.ms方法(该方法现由benchmarkgem 提供); - 移除
Time实例与ActiveSupport::TimeWithZone相加的废弃支持; - 移除
to_time保留系统本地时区的废弃支持——现在始终保留接收者的时区。
废弃(Deprecations):
- 废弃
config.active_support.to_time_preserves_timezone配置项(与上一条移除配套); - 废弃
String#mb_chars与ActiveSupport::Multibyte::Chars; - 废弃
ActiveSupport::Configurable模块。
Active Job
移除(Removals):
- 移除将
ActiveJob::Base.enqueue_after_transaction_commit设为:never、:always、:default的支持; - 移除已废弃的
Rails.application.config.active_job.enqueue_after_transaction_commit配置项; - 移除内置的
SuckerPunch适配器(内部实现),改用sucker_punchgem 自带的适配器。
废弃(Deprecations):
- 自定义 Active Job 序列化器必须提供公开的
#klass方法; - 废弃内置
sidekiq适配器(改由sidekiqgem 提供)。
Action Text
废弃(Deprecations):
- 废弃
ActionText::TrixAttachment类; - 废弃
ActionText::Attachments::TrixConversion模块; - 废弃
ActionText::Attachable#to_trix_content_attachment_partial_path,请改为覆写to_editor_content_attachment_partial_path; - 废弃
ActionText::RichText#to_trix_html与ActionText::Content#to_trix_html。
其他框架
Action Cable、Action Mailer、Action Mailbox 与 Active Model 在本版本发布说明中没有列出需要额外注意的移除或废弃项,详细行为变化以各自 CHANGELOG 为准。
小结
Rails 8.1 的主题可以概括为“把长生命周期任务与观测性做扎实”:Active Job Continuations 让长作业在部署滚动中不再丢进度;Event Reporter 让应用事件以结构化形式进入日志与采集管线;本地 CI 把验证循环拉回开发者机器;废弃关联、Markdown 渲染与命令行凭据获取则分别服务于渐进式重构、AI 时代的内容交换与更轻量的部署密钥管理。升级前建议先按升级指南过一遍 Action Pack 参数解析与 Active Support 时间/多字节字符串相关的行为变化,这些是最可能在存量应用中触发的兼容性问题。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00