複数CRON生成を検出して警告する機能を追加

rails/solid_queue

Solid Queueは、自然言語で記述されたスケジュールが複数のCRON式を生成する場合、最初のCRON式のみを使用し残りを無視していました。本PRは、この問題を検出してバリデーションエラーを発生させる機能を追加します。

背景

Solid QueueはFugitライブラリを使用してスケジュールをパースします。every day at 00:40 and 15:20 のような自然言語表記は、異なる分数を持つ複数の時刻を指定するため、単一のCRON式では表現できません。この場合、Fugitは最初の時刻(40 0 * * *)のみを返し、2番目の時刻(15:20)は無視されていました。#703 がこの問題を報告しています。

同じ分数を持つ複数の時刻(every day at 00:40 and 15:40)は 40 0,15 * * * のように単一のCRON式で表現できるため問題になりません。しかし、異なる分数を持つ場合は複数のCRON式が必要であり、それが暗黙的に切り捨てられることでタスクの実行漏れが発生していました。

技術的な変更

app/models/solid_queue/recurring_task.rbparsed_schedule メソッドが、multi: :fail オプションを指定するよう変更されました。

変更前:

def parsed_schedule
  @parsed_schedule ||= Fugit.parse(schedule)
end

変更後:

def parsed_schedule
  @parsed_schedule ||= Fugit.parse(schedule, multi: :fail)
end

multi: :fail を指定すると、Fugitが複数のCRON式を生成する必要がある場合に ArgumentError を発生させます。この例外を ensure_schedule_supported バリデーションメソッドでキャッチし、エラーメッセージに変換します。

def ensure_schedule_supported
  unless parsed_schedule.instance_of?(Fugit::Cron)
    errors.add :schedule, :unsupported, message: "is not a supported recurring schedule"
  end
rescue ArgumentError => error
  message = if error.message.include?("multiple crons")
    "generates multiple cron schedules. Please use separate recurring tasks for each schedule, " +
    "or use explicit cron syntax (e.g., '40 0,15 * * *' for multiple times with the same minutes)"
  else
    error.message
  end

  errors.add :schedule, :unsupported, message: message
end

エラーメッセージは、問題の原因と解決策の両方を提示します。複数のタスクに分割するか、同じ分数なら明示的なCRON構文(40 0,15 * * *)を使用するよう案内します。

test/models/solid_queue/recurring_task_test.rb には、以下の3つのケースをカバーするテストが追加されました。

  • 単一の時刻指定(every day at 00:40)は有効
  • 同じ分数の複数時刻(every day at 00:40 and 15:40)は単一CRON式 40 0,15 * * * として有効
  • 異なる分数の複数時刻(every day at 00:40 and 15:20)は無効で、適切なエラーメッセージが表示される
task = recurring_task_with(class_name: "JobWithoutArguments", schedule: "every day at 00:40 and 15:20")
assert_not task.valid?
error_message = task.errors[:schedule].first
assert_includes error_message, "generates multiple cron schedules"
assert_includes error_message, "Please use separate recurring tasks"

設計判断

Fugitの multi: :fail オプションを活用する方式 が採用されました。

この判断により、複数CRON検出のロジックをSolid Queue側で実装する必要がなくなり、Fugitライブラリの既存機能を活用できます。ArgumentError の発生をバリデーション層でキャッチしてユーザーフレンドリーなメッセージに変換することで、エラーハンドリングとユーザビリティの両立を実現しています。

エラーメッセージには、問題の説明だけでなく具体的な解決策(タスク分割またはCRON構文の使用)を含めることで、ユーザーが次のアクションを判断しやすくなっています。バリデーションメソッド名も supported_schedule から ensure_schedule_supported へリネームされ、検証内容がより明確になりました。

まとめ

本PRは、Fugitの multi: :fail オプションを活用して、複数CRON式を生成するスケジュール設定を検出し、バリデーションエラーとして報告する機能を追加しました。これにより、タスクの実行漏れが設定時に発見できるようになり、複数の時刻でタスクを実行したい場合の正しい設定方法が明示されます。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

「リード文→背景→技術的な変更→設計判断→まとめ」という総論→各論→結論の構成が明確であり、ガイドラインに準拠しています。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト(```言語:ファイルパス)およびGitHubのPR・Issueへのリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Solid Queueの内部実装やFugitライブラリに関する内容であり、専門知識を持つエンジニアという対象読者に適した技術レベルで記述されています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論の構成になっており、各段落はトピックセンテンスで始まり、1段落1トピックの原則が守られています。可読性が非常に高いです。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されているコードブロックは、提供されたDiff情報と完全に一致しており、ファイル名も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「Fugit」「CRON」「ArgumentError」などの技術用語が文脈に応じて正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

「multi: :fail」オプションの挙動や、バリデーションでの例外補足に関する説明は、Diffの内容と整合しており技術的に正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張は、PRのDescriptionやDiff内のコード変更によって裏付けられており、ハルシネーション(捏造)は見られません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#707)、Issue番号(#703)などの数値や固有名詞はすべて正確に記載されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルはPRの主題「複数CRON生成を検出して警告する」を正確に反映しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

PR情報に含まれない外部知識(バージョンのサポート状況やリリース日程など)の追加はなく、事実に基づいた記述に徹しています。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「既に」「近い将来」といった時間表現の歪曲はなく、事実関係が正確に記述されています。