こんにちは、hachi8833です。Rails 7.2.0のマイルストーン↓は、先週ぐらいには残り3つだったのが7つになったり5つになったりと変動しつつ、さっき見たら残り2つになっていました。
参考: 7.2.0 Milestone
🔗Rails: 先週の改修(Rails公式ニュースより)
- 公式更新情報: Ruby on Rails — Rails community survey, improved association validation errors and more
- 公式更新情報: Ruby on Rails — Start of Rails 8 development, 7.0.8.2 & 7.1.3.3 released, Kamal by default, and lots more!
🔗 index_errors
にnested_attributes_order
モードを追加してバグを修正
- 関連付けバリデーションのエラーで
index_errors
のインデックス生成が誤っていたのを修正。lulalala
index_errors: :nested_attributes_order
モードを追加。ネステッド属性のセッターで受け取った順序に基づいて関連付けバリデーションエラーのインデックスを作成し、
reject_if
設定を尊重するようになる。これにより、APIはバリデーションエラーをフォームの個別のフィールドに対応付けるのに十分な情報をフロントエンドに提供可能になる。lulalala
同Changelogより
- 以下の関連付けバリデーションエラーのバグを修正
index_errors does not consider indexes of correct existing sub records #24390index_errors: :nested_attributes_order
モードを追加し、エラーの順序を変更可能にした動機/背景
GitLabでは
index_errors
を使っているが、バグを回避しなければならない(issueは#24390)。 このバグは、関連付けレコードの不完全なコレクションからインデックスが算出される可能性があるために、不正なインデックスが含まれる関連付けのバリデーションエラーに関係している。これを修正するには、関連付けの完全なコレクションからインデックスを作成する。@tijwelchが最初に#24728を作成したが、@markedmondsonによって
reject_if
が正しく動作しないと指摘された。また、議論の結果、「インデックス作成(indexing)」の意味付けに以下の2つの異なる解釈があることが判明した。
- (データベースの順序に基づいて)関連付け順序のインデックスを作成する。これは現在の振る舞い。
- インデックスをネステッド属性の順序で作成する(
reject_if
はここにしか適用できない)この2つの目標は互いに相反している。この両方に対応するために、このプルリクでは
nested_attributes_order
という新しい順序付けモードを追加している。このモードは、GitLabのユースケース(フロントエンドがネステッド属性を任意の順序で渡す可能性があるが、その順序に一致するエラーインデックスが引き続き必要)により適している。詳細
このプルリクで追加する新しい
ActiveRecord::Associations::NestedError
クラスは、インデックスの算出を処理する。元のインデックスロジックはAutosaveAssociation
にあったが、このクラスに移動した。インデックス作成をassociation.target
順にするかネステッド属性順にするかは、index_errors
設定に基づいて選択する。ネステッド属性順序については、
nested_attributes
が(roles_attributes=
などのセッターで)代入されると、それに対応するレコードの配列がnested_attributes_target
として関連付けオブジェクトに保存される。続いて、NestedError
がこの配列にアクセスしてインデックスを算出可能になる。reject_if
も動作する(何かがrejectされた場合はnested_attributes_target
にプレースホルダとしてnil
が置かれるので、インデックス全体が維持される)。
同PRより
つっつきボイス:「GitLabからのプルリクなんですね」「Rails 5.0のときに以下のプルリクでindex_errors: true
を指定可能になっていた↓けど、今回のプルリクでネステッド属性かどうかで振る舞いを変えるためにindex_errors: :nested_attributes_order
も指定可能になったのね: 特殊な要件に見えるけど実は一般性の高いニーズかもしれない」
参考: Errors can be indexed with nested attributes by mprobber · Pull Request #19686 · rails/rails
🔗 SKIP_TEST_DATABASE_TRUNCATE
環境変数でマルチプロセスのテスト実行を高速化可能になった
ENV["SKIP_TEST_DATABASE_TRUNCATE"]
フラグを追加。これは、すべてのテストがデフォルトのトランザクション内で実行される、巨大なDB上のマルチプロセステストをスピードアップする。これによりHEYでは、178個のテーブルに対して24個のプロセスで実行されるテストを最大10秒短縮できた(最大4000個のテーブルのTRUNCATEがスキップ可能になったおかげ)。
同PRより
つっつきボイス:「DHHによるプルリクです」「TRUNCATEしなくてもいいテーブルをスキップすることでテストを高速化した: 普通ならdatabase_cleaner gemを使うところかなと思うけど、DHHならこうやってRailsに反映する方が話が早いでしょうね」「修正もとてもシンプルですね」
# activerecord/lib/active_record/tasks/database_tasks.rb#L386
def reconstruct_from_schema(db_config, format = ActiveRecord.schema_format, file = nil) # :nodoc:
file ||= schema_dump_path(db_config, format)
check_schema_file(file) if file
with_temporary_pool(db_config, clobber: true) do
if schema_up_to_date?(db_config, format, file)
- truncate_tables(db_config)
+ truncate_tables(db_config) unless ENV["SKIP_TEST_DATABASE_TRUNCATE"]
else
purge(db_config)
load_schema(db_config, format, file)
end
rescue ActiveRecord::NoDatabaseError
create(db_config)
load_schema(db_config, format, file)
end
end
🔗 リクエストログのアロケーションカウントをGCタイムに置き換えた
アロケーションカウントはパフォーマンスにおいて興味深い指標になることが多いが、スレッドごとのメトリクスではないため、リクエストログに含める関連性が必ずしも高いとは限らず、マルチスレッド環境ではレポートが広範囲にわたって不正確になる。
Ruby 3.1からは、GCで消費した時間を単調増加するカウンタである
GC.total_time
が追加された。これは実際はスレッドごとのメトリクスではないが、レスポンスタイムと単位が同じになるのでより興味深い値となり、GCが一時停止したときのパフォーマンス問題がいつ発生したかを確認しやすくなる。同PRより
つっつきボイス:「以前のUnicornでマルチプロセス・1スレッドで動かしているような環境ならリクエストログにアロケーションカウントを出力する意味はあると思うけど、マルチスレッドで動くPumaがRailsのデフォルトになった現代だとアロケーションカウントは微妙になってくるというのはわかる」
# actionpack/lib/action_controller/log_subscriber.rb#L36
- additions << "Allocations: #{event.allocations}"
+ additions << "GC: #{event.gc_time.round(1)}ms"
🔗 ドキュメント: 「生成されるDockerfileはproduction用です」
# railties/lib/rails/generators/rails/app/templates/Dockerfile.tt#L3
+# Note: This Dockerfile is optimized for production deployment and isn't a good base
+# for development enviroments.
つっつきボイス:「プルリクのタイトルだけでわかる内容」「自分もrails new
で生成されるDockerfileを開発用に使っていいのかどうかが気になってたんですが、おかげでスッキリしました😋」「開発用のDockerコンテナは例のdevcontainer(ウォッチ20240228)でやっていく流れっぽいですね」
🔗 RAILS_ENV
が未設定の場合にminitestが正常に動作するよう修正
- PR: Don't mess with Minitest unless RAILS_ENV is set by tenderlove · Pull Request #51718 · rails/rails
Minitestは、インストール済みのgemをすべて自動的にスキャンしてからプラグインをそれらのgemから読み込む。Railsアプリのコンテキスト内で実行されているかどうかを検出して、その場合にのみMinitestの振る舞いを変更しなければならない。
bin/rails
経由で実行されているかどうかを判断するには、RAILS_ENV
が設定済みかどうかを確認するだけでよい。Ref: minitest/minitest#996
Ref: minitest/minitest#725同PRより
つっつきボイス:「tenderloveさんによる修正です」「コードがbin/rails
で実行されたものかどうかを判定するにはRAILS_ENVが設定済みかどうかをチェックすればいいのか!」「Railsの中の人だけが知ってる情報という感じですね」
参考: 4 Rails環境の設定 -- Rails アプリケーションの設定項目 - Railsガイド
🔗 関連付けのquery_constraints:
オプションが非推奨化された
- PR: Warn about changing
query_constraints:
behavior by nvasilevski · Pull Request #51571 · rails/rails
関連付けの
query_constraints
オプションが非推奨化された。今後はforeign_key
を使うこと。Nikita Vasilevsky
同Changelogより
このプルリクは、関連付けの
query_constraints:
オプションに非推奨警告を追加する。このオプションは今後のRailsバージョンで振る舞いが変更されるため、アプリケーションは現在の振る舞いを維持するためにforeign_key:
に切り替えることが推奨される。関連issue: #49671 (comment)
非推奨化メッセージを出力するタイミング
現在のRailsは非推奨警告を起動時に出力するが、
query_constraints
によるリフレクションが利用されている場合に限って実行時に非推奨警告を出力するよう変更するオプションがある。
自分がこれをinitialize
に置いた理由は、可能なソリューションの中で最もシンプルだからに過ぎない。これを実行時に移動するには少しリネームが必要になる。
この非推奨警告を実行時に出力する理由があると思う方や、あるいは起動時と実行時の両方で出力すべきと思う方は知らせて欲しい。テスト用モデルについて
この非推奨警告がテストで表示されないようにするため、すべてのモデルを
foreign_key:
に移行したが、長期的な目的は、Sharded::
名前空間モデルでquery_constrainsts
とCpk::
の新しい振る舞いを用いてforeign_key
を利用し続けることである。また、CIがパスすれば
foreign_key
が実際にquery_constraints:
を安全に置き換えられることが保証される。
同PRより
つっつきボイス:「issueを見ると、query_constraints:
とforeign_key
の振る舞いを切り離す処置の一環ということみたい: query_constraints:
オプションを使ったことあったかどうかはすぐ思い出せないけど、いずれにしろ非推奨化されるのであればチェックは必要でしょうね」
参考: 4.3.2.13 :query_constraints
-- Active Record の関連付け - Railsガイド
🔗 active_job.queue_adapter
の設定がすべてのテストに反映されるよう修正
すべてのテストが
active_job.queue_adapter
コンフィグを尊重するようになった。修正前は、
config/application.rb
やconfig/environments/test.rb
でconfig.active_job.queue_adapter
を設定していても、選択したしたアダプタがすべてのテストで共通して使われるとは限らなかった。テストでは、指定したアダプタが使われることもあればTestAdapter
が使われることもあった。Rails 7.2では、
queue_adapter
コンフィグが設定されていればそれを尊重するようになった。設定が提供されていない場合はTestAdapter
が使われる。詳しくは#48585を参照。
Alex Ghiculescu
同Changelogより
つっつきボイス:「queue_adapter
で別のアダプタを指定したのにTestAdapter
が使われることがあったというバグを修正した、なるほど」「プルリクメッセージがみっちり書かれていますね↓」
動機/背景
config/application.rb
やconfig/environments/test.rb
でconfig.active_job.queue_adapter = 何らかのアダプタ
を設定しても、一部のテストケースで反映されないことがある。具体的には、
ActionDispatch::IntegrationTest
、ActionMailer::TestCase
、ActiveJob::TestCase
でテストアダプタがTestAdapter
に設定され、それ以外のテストケースではInlineAdapter
に設定される。ある環境にテストアダプタを設定すれば、そのテストアダプタが環境内のどこでも利用可能になることが期待されるだろう。たとえば、test環境でDelayed Jobアダプタを使うと、テストコードをproduction環境にさらに近づけることが可能になる。#37270のフィードバックもこれに倣っているが、特定のクラスでテストアダプタを無効にするという回避策は信頼できないことが示唆されている。
詳細
ジョブで使われるキューアダプタを決定するロジックは非常に込み入っている。
以下は現在の
main
ブランチでの動作。
- テストを実行するときに、そのテストのクラスが
ActiveJob::TestHelper
をinclude
しており、かつqueue_adapter_for_test
をオーバーライドしているのであれば、queue_adapter_for_test
のアダプタが使われる。- そうでない場合: テストを実行するときに、そのテストのクラスが
ActiveJob::TestHelper
をinclude
してる場合は:test
アダプタが使われる。- そうでない場合: ジョブクラスで
self.queue_adapter
が設定されている場合は、そのアダプタが使われる。- そうでない場合: ジョブのスーパークラス(
ApplicationJob
など)でself.queue_adapter
が設定済みの場合は、そのアダプタが使われる。- フォールバックする。
- ユーザー設定がない場合は、
Rails.application.config.active_job.queue_adapter
はデフォルトの:async
になる。- Railsのコンフィグが未設定の場合(Active Jobが単独で使われる場合など)は
:async
にフォールバックする。上に記したように、
ActiveJob::TestHelper
は組み込みテストクラスの一部でしかinclude
されていない。しかも、特定のジョブクラスでのみキューアダプタを指定する状況はめったにない(通常、異なるバックエンドで異なるジョブが実行されるのは望ましくない)。つまり実際には、development環境やproduction環境ではオプション5が使われる。test環境では、テストの種類に応じてオプション2または5が使われる。このプルリクは、ロジックを以下のように変更する。
- ジョブクラスに
self.queue_adapter
が設定されている場合は、そのアダプタが使われる。- そうでない場合: ジョブのスーパークラス(
ApplicationJob
など)でself.queue_adapter
が設定済みの場合は、そのアダプタが使われる。- テストを実行したときに、そのテストのクラスが
ActiveJob::TestHelper
をinclude
しており、かつqueue_adapter_for_test
をオーバーライドしているのであれば、queue_adapter_for_test
のアダプタが使われる。- フォールバックする。
- ユーザー設定がない場合は、
Rails.application.config.active_job.queue_adapter
は、Rails.env.test?
がtrueならデフォルトで:test
に、そうでなければデフォルトの:async
になる。- テストを実行するときに、そのテストのクラスが
ActiveJob::TestHelper
をinclude
しており、かつRailsのコンフィグが未設定の場合(Active Jobが単独で使われる場合など)は、:test
が使われる。- それ以外の場合は、
:async
にフォールバックする。重要な変更点は以下のとおり。
- これによって、ジョブクラスに
queue_adapter
が設定されていれば常にその設定が尊重されるようになる。
queue_adapter_for_test
のオーバーライドも引き続き有効だが、特定のジョブクラスに設定されたキューアダプタよりも優先されなくなった。どちらもめったに使われないと思えるし、この変更の影響はtest環境にとどまるので、比較的安全な変更だと思われる。- Railsコンフィグのデフォルトキューアダプタが環境に応じて変わるようになった(
test
環境なら:test
に、それ以外はすべて:async
に)。デフォルトのRails環境テンプレートは、production環境でのみ特定のキューアダプタを設定することが示されている。これに該当するユーザーにとって、このプルリクで振る舞いは変わらない。
より細かな指定を行っているユーザーの場合は、ジョブキューアダプタごとに設定すれば、このプルリクで期待通りの振る舞いを引き続き得られるはず。
実際の変更は、すべての環境でデフォルトのキューアダプタを設定しているユーザーが対象となる(#37270)。このようなユーザーにとっては、指定のキューアダプタが一部のテストで使われるが他のテストでは使われないという問題がこのプルリクによって解決される。
追加情報
修正: #37270
参考: bensheldon/good_job#846
参考: #26360 -- このプルリクはここでの振る舞いを変更するが、自分にはやりすぎのように思える内部問題を修正するため、この作業中に他のいくつかのプルリク(#48623、#48626)から抽出した。
また、このプルリクの前に#48599からも議論の余地のない修正として抽出した。
同Changelogより
🔗 Rails 8からKamalがデフォルトのデプロイツールになる
デプロイにKamalをデフォルトで利用するようになり、Rails固有のconfig/deploy.ymlも生成するようになる。
--skip-kamal
でスキップ可能。詳しくはhttps://kamal-deploy.org/を参照。DHH
同Changelogより
つっつきボイス:「これもDHHによるプルリクですね」「お、Rails 8からはデフォルトでKamalでデプロイするようになるのか: 自分のように既にECSやCodeDeploy等でRailsアプリをデプロイする他の方法を確立済みの人たちにはKamalは不要かなと思いますけど、--skip-kamal
でスキップできるようですし、そうでなくても手動で消せば済むので別にいいかな」
前編は以上です。
バックナンバー(2024年度第2四半期)
- 20240513前編 Railsコンソールが最新のIRB APIに移行、assertionless_tests_behaviorほか
- 20240426後編 Prismの歴史と現況を振り返る、Steepの"narrowing"実装の内部ドキュメントほか
- 20240425前編 RailsからOpenStructを削除、Playwrightベストプラクティスほか
- 20240423後編 Kamalはゲームチェンジャーになるか、Solid Queueで使われているfugitほか
- 20240416前編 ジョブのエンキューをトランザクション完了時まで自動先延ばしほか
- 20240410後編 SeleniumでRubyの全クラスとモジュールにRBSが追加ほか
- 20240409前編 Rails公式の"rails-new"ツールでRailsプロジェクトをセットアップほか
- 20240402 solid_queueとmission_control-jobsが正式にRailsのgemに、Rubyの"チルド"文字列ほか
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)