2025 年 10 月 22 日,星期三

Rails 8.1:Job continuations、结构化事件、本地 CI

发布者:rafaelfranca

Rails 8.1 代表了自上次主要版本发布以来,由超过 500 名贡献者通过 2500 次提交所完成的工作。在人们试用了 beta 版和 release candidates 版本几周后,我们很高兴地发布最终版本。

此版本展示了 Rails 的稳定性,Shopify 和 HEY 等应用程序已经运行了数月。

以下是一些亮点:

Active Job Continuations

现在可以将长时间运行的任务分解为离散的步骤,以便在重启后从最后一个已完成的步骤继续执行,而不是从头开始。当使用 Kamal 进行部署时,这尤其有用,因为 Kamal 默认只给运行任务的容器三十秒的关闭时间。

示例

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

Active Job Continuations 由37signals的 Donal McBreen 主导。

结构化事件报告

Rails 中的默认日志记录器非常适合人类阅读,但不太适合事后处理。新的 Event Reporter 为 Rails 应用程序生成结构化事件提供了一个统一的接口。

Rails.event.notify("user.signup", user_id: 123, email: "user@example.com")

它支持为事件添加标签。

Rails.event.tagged("graphql") do
  # Event includes tags: { graphql: true }
  Rails.event.notify("user.signup", user_id: 123, email: "user@example.com")
end

以及上下文。

# All events will contain context: {request_id: "abc123", shop_id: 456}
Rails.event.set_context(request_id: "abc123", shop_id: 456)

事件会分发给订阅者。应用程序注册订阅者来控制事件的序列化和分发方式。订阅者必须实现一个 #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

结构化事件报告由Shopify的 Adrianna Chang 主导。

本地 CI

开发人员的机器现在速度非常快,拥有大量的核心,这使得它们非常适合运行本地测试,即使是相对大型的测试套件。以前,HEY拥有超过 30,000 个断言的测试套件,在云端运行时,算上协调、镜像构建和并行运行,需要超过 10 分钟。现在,在 Framework Desktop AMD Linux 机器上本地运行只需要 1 分 23 秒,在 M4 Max 上需要 2 分 22 秒。

这使得摆脱所有 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 运行才能合并。

本地 CI 工作由37signals的 Jeremy Daer 主导。

Markdown 渲染

Markdown 已成为 AI 的通用语言,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

命令行凭据获取

Kamal 现在可以轻松地从加密的 Rails 凭据存储中获取用于部署的密钥。这使其成为外部密钥存储的一个低成本替代方案,只需要 master key 即可工作。

# .kamal/secrets
KAMAL_REGISTRY_PASSWORD=$(rails credentials:fetch kamal.registry_password)

这是Shopify的 Matthew Nguyen 和 Jean Boussier 的工作。

已弃用的关联

Active Record 关联现在可以标记为已弃用。

class Author < ApplicationRecord
  has_many :posts, deprecated: true
end

这样,将会报告对 posts 关联的使用情况。这包括显式的 API 调用,例如:

author.posts
author.posts = ...

和其他用法,以及间接用法,例如:

author.preload(:posts)

通过嵌套属性的使用等。

支持三种报告模式(:warn:raise:notify),并且可以启用或禁用回溯,尽管您始终会获得报告使用情况的位置。默认值为 :warn 模式和禁用回溯。

这是 Xavier Noria 在为Gusto提供咨询时上游提交的。

无注册表 Kamal 部署

Kamal 不再需要像 Docker Hub 或 GHCR 这样的远程注册表来进行基本部署。默认情况下,Kamal 2.8 现在将使用本地注册表进行简单部署。对于大规模部署,您仍然需要使用远程注册表,但这使得入门更容易,并能将您的第一个“Hello World”部署发布到实际环境中。

以及其他所有内容

自上次发布以来,Rails 8.1 包含了 2500 次提交,其中包含大量的修复、次要功能和改进。请查看 CHANGELOG 文件以获取详细信息。