10月23日、「Rails 8.1がリリースされた)」。
この記事では、Rails 8.1における主要な新機能であるジョブ継続機能(Job Continuations)、構造化イベント(Structured Events)、ローカルCI(Local CI)など、開発体験を大幅に向上させるアップデートについて詳しく紹介する。
概要
Rails 8.1は、前回のメジャーリリース以降、500人以上のコントリビューターによる2,500件超のコミットを経て公開された安定版リリースである。ShopifyやHEYといった大規模アプリケーションでは、すでに本番環境で数か月間稼働しているという。
Active Job Continuations:長時間ジョブの継続実行
これまで再起動などによって中断されたジョブは、最初から再実行する必要があった。Rails 8.1では、ジョブを「ステップ」に分割し、中断時に最後の完了ステップから再開できるようになった。
特にKamalによるデプロイ環境では、ジョブを実行するコンテナがデフォルトで30秒以内にシャットダウンされるため、この仕組みが有効に機能する。
class ProcessImportJob < ApplicationJob
include ActiveJob::Continuable
def perform(import_id)
@import = Import.find(import_id)
# 初期化ステップ
step :initialize do
@import.initialize
end
# カーソル付き処理ステップ
step :process do |step|
@import.records.find_each(start: step.cursor) do |record|
record.process
step.advance! from: record.id
end
end
# 最終ステップ
step :finalize
end
private
def finalize
@import.finalize
end
end
この機能は、37signals の Donal McBreen によって主導された。
Structured Event Reporting:構造化イベントの導入
従来のRailsロガーは人間が読むには適していたが、機械による後処理には不向きだった。そこで新たに、構造化イベントを生成するための「Event Reporter」APIが追加された。これにより、アプリケーション内の重要なイベントを一貫した形式で通知・処理できる。
Rails.event.notify("user.signup", user_id: 123, email: "user@example.com")
イベントにはタグやコンテキストも付与可能だ。
Rails.event.tagged("graphql") do
Rails.event.notify("user.signup", user_id: 123, email: "user@example.com")
end
Rails.event.set_context(request_id: "abc123", shop_id: 456)
通知を受け取る「サブスクライバー」は #emit
メソッドを実装し、出力形式を制御する。
class LogSubscriber
def emit(event)
payload = event[:payload].map { |k, v| "#{k}=#{v}" }.join(" ")
log = "[#{event[:name]}] #{payload} at #{event[:source_location][:filepath]}:#{event[:source_location][:lineno]}"
Rails.logger.info(log)
end
end
この開発は Shopify の Adrianna Chang が主導した。
Local CI:クラウドに依存しない高速テスト実行
開発マシンの性能向上を背景に、Rails 8.1ではローカル実行可能なCI(継続的インテグレーション)DSLが標準搭載された。
HEYのテストスイート(3万以上のアサーション)を例にすると、クラウド環境では10分以上かかっていた処理が、ローカルではAMDデスクトップで1分23秒、M4 Maxでも2分22秒で完了する。
設定は 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"
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
この仕組みは 37signals の Jeremy Daer によって開発された。
Markdown Rendering:Markdown対応の強化
Markdown形式のレスポンス生成が容易になった。次のように respond_to
ブロックで format.md
を追加するだけで、Markdownを直接返すことができる。
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
Command-line Credentials Fetching:Kamalとの統合
デプロイツールKamalが、Railsの暗号化クレデンシャルストアからシークレット情報を直接取得できるようになった。外部のシークレット管理システムを使わず、マスターキーのみで運用が可能になる。
# .kamal/secrets
KAMAL_REGISTRY_PASSWORD=$(rails credentials:fetch kamal.registry_password)
この機能は Shopify の Matthew Nguyen と Jean Boussier による貢献である。
Deprecated Associations:非推奨アソシエーションの検出
Active Recordのアソシエーションを「非推奨」としてマークできるようになった。これにより、使用時に警告・例外・通知を発生させることができる。
class Author < ApplicationRecord
has_many :posts, deprecated: true
end
呼び出し例:
author.posts
author.posts = [...]
報告モードは :warn
、:raise
、:notify
の3種類があり、デフォルトでは警告モードとなる。
この機能は Gusto 向けコンサルティング中の Xavier Noria により上流に統合された。
詳細はRails 8.1: Job continuations, structured events, local CIを参照していただきたい。