- 現時点(2024/08/30)でRails7.2.1がリリースされています
- Ruby on Rails 7.2 Release Notes — Ruby on Rails Guides にメジャーフィーチャーが書かれている
- マイナーフィーチャーはよくわからないので、個人的に気になったマイナーフィーチャをまとめています
- Implement the bin/rails boot command by fxn · Pull Request #52480 · rails/rails
- ベンチマークを取るようなときに「railsをbootさせる」だけをしたいケースが有る
- これまでは
bin/rails runner 1
みたいなやつでやってたけど専用のがあったほうがいいよね、ということで追加された - Implement the bin/rails boot command by fxn · Pull Request #52480 · rails/rails
- CIで「railsが起動できること」をチェックするときに使えると言っている人もいる
- Add
BACKTRACE
env variable to turn off backtrace by ghiculescu · Pull Request #50563 · rails/rails - DHHがIssueを立てた
- Add BACKTRACE env variable to turn off backtrace for normal running, not just tests · Issue #50440 · rails/rails
- テスト環境でフルのバックトレースがほしい+通常の開発環境でも使えることというオーダー
- Do not generate pidfile in production environments by hschne · Pull Request #50644 · rails/rails
- rails newしたときに生成されるpumaの設定が変更された
- Docker環境でOOMにアプリケーションサーバが殺されて再起動するときにpidファイルが残っていて起動できず、手動で削除するというオペレーションが発生するのを避けたいという話
- これを解決するためにdevelopment環境だけpidファイルを作るという設定になった
- Docker環境である、ないに関わらずdevelopment環境以外では作らない
- 環境変数
ENV["PIDFILE"]
で明示的にpidファイルを設定することはできるので、もしpidファイルが必要なプロジェクトがあったらPIDFILEを設定しておくと良さそう- PIDFILEによるpidファイルの指定はRails7.1でも有効
- Update rails console prompt by st0012 · Pull Request #50825 · rails/rails
- 次のように"アプリケーション名(環境名の短縮形)"になる
- test, development, production以外の環境は短縮されない
my-app(dev)>
ErrorReporter#unexpected
to report in production but raise in development by byroot · Pull Request #49951 · rails/rails- Exceptional Rails by Shinichi Maeshima - Kaigi on Rails 2023 で紹介したErrorReporterの機能追加
- Error Reporting in Rails Applications — Ruby on Rails Guides を読むとわかるように、これまで
begin...rescue Sentryなどにエラーを通知する; end
としていたところをもっとスッキリとしたインタフェースで書き直せる - しかし、これを使うとapp/ 配下で直接SentryのAPIを叩くことはなくなるかというとそうではない
- ↓みたいに、例外オブジェクト無しで文字列だけ送っておきたい、というユースケースがある
Sentry.capture_message(
"User's email wasn't provided",
level: :warning
)
- これもErrorReporterで対応できたらいいな〜と思っていたけど、"ErrorReporter"なのに文字列だけ送りたいメソッド生やすの無理かな…となって諦めていた
- そこでこのPR
- 差分を見るとわかるように「ここに来ることは想定していないなんですよ」という状況を例外としてRuntimeErrorオブジェクトを作り、本番環境ではreportメソッドを呼び出している
- 開発環境やテスト環境では例外にしている
- なので「ここに来ることは想定していないなんですよ」以外で文字列を送りたい場合は引き続きSentryなどが提供しているやつを引き続き直接使う必要はある
- が、ほとんどのケースはこれだと思うので直接Sentryなどを参照することはほぼなくなるはず…
- うまいことやったな、という印象
- Add queries count to template rendering instrumentation by fatkodima · Pull Request #51457 · rails/rails
- クエリ回数と、そのうちクエリキャッシュが使われたのが何回なのかがログから簡単に確認できるようになった
- N+1対応をするときなど便利ですね
# Before
Completed 200 OK in 3804ms (Views: 41.0ms | ActiveRecord: 33.5ms | Allocations: 112788)
# After
Completed 200 OK in 3804ms (Views: 41.0ms | ActiveRecord: 33.5ms (2 queries, 1 cached) | Allocations: 112788)
class Current < ActiveSupport::CurrentAttributes
attribute :counter, default: 0
end
- CurrentAttribute使いには朗報では
- Rails UJS has been deprecated since Rails 7, time to die by dhh · Pull Request #50535 · rails/rails
- Rails7.2からはrails-ujsは同梱されなくなった
- npmパッケージとしては7.1系がまだあるけど、Rails7.2以降はリリースされない
- いまでもrails-ujsを使っている人は、枯れているライブラリなのでRails7.2で直ちに困る、ということはないと思うけど、徐々にturboないし他のライブラリに移行するのが良さそう
- Ignore implicit locals if not declared by templates with strict locals · rails/rails@110206a
- Rails7.1では Allow templates to define which locals they accept by joelhawksley · Pull Request #45602 · rails/rails で部分テンプレート中に使うローカル変数をマジックコメントで宣言することができるようになった
- めっちゃべんり
- なんだけど、例えば
render @posts
のようにコレクションを渡したときに困る- コレクションをレンダリングする際には、例えば↑の例であれば
post_counter
,post_iteration
というローカル変数が自動的に使えるような仕様になっているのだった- これに引っかかって意図せずエラーになってしまう
- counterもiterationも、何番目にレンダリングされたかを表すもの
- counterは単純に数値が入っている(後方互換性のために残されている)
- iterationは
first?
やlast?
、size?
などが生えていて高機能
- コレクションをレンダリングする際には、例えば↑の例であれば
- なのでこれらのローカル変数をstrict localで指定しない限り部分テンプレートに渡さないようになった
- Add rate limiting to Action Controller via the Kredis limiter type by dhh · Pull Request #50490 · rails/rails
- ドキュメント
- 簡易的なrack-attackとして使える
- Support
RETURNING
clause for MariaDB by fatkodima · Pull Request #49840 · rails/rails - insert_allのオプションにreturningというのがある
- insertに成功した列の値を返すときに、なんの値を返すかを指定できるオプション
- このオプションはpostgres限定だったのだけど、mariadb 10.5.0からこの構文に対応したらしくRailsでも使えるようになった
- Make the output of
ActiveRecord::Core#inspect
configurable. by andrewn617 · Pull Request #49765 · rails/rails - 本番環境で複数のActiveRecordオブジェクトをinspectした結果、Active Support Parameter Filterの実行でめっちゃ時間がかかって9sかかった、という事案がshopifyであったらしい
- これを解決するために、そもそもモデルをinspectしたときに出力する属性を制限する、という修正が入った
- デフォルトだとidだけになる
Post.first.inspect #=> "#<Post id: 1>"
attributes_for_inspect
をゴニョると挙動を変更できる
Post.attributes_for_inspect = [:id, :title]
Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!">"
全部表示したいぞ、というときは:all
にする
Post.attributes_for_inspect = :all
Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
- 開発環境とテスト環境ではデフォルト
:all
- 一時的に全部表示したいとき用に
full_inspect
メソッドも用意されている - これ厳密には非互換な変更なんじゃないの、という気がするけどとくに段階的移行のためのなにかはない
- まあinspectの振る舞いに依存しているコードとか普通ないやろ、というのはわかる
- Add
explain
support for methods likelast
,pluck
andcount
by p8 · Pull Request #50482 · rails/rails - countとかpluckなどのメソッドは即クエリを発行してしまうので、explainをつなげることができなかった
- そこで、
User.all.explain.count
のようにできるようにした、というのがこのPR- 差分を見ると対応しているメソッド一覧がわかる
- Add
explain
support for methods likelast
,pluck
andcount
by p8 · Pull Request #50482 · rails/rails - average, count, first, last, maximum, minimum, pluck, sum に対応
- Add
- 差分を見ると対応しているメソッド一覧がわかる
- べんり
- Infer
:inverse_of
forhas_many ..., through:
by seanpdoyle · Pull Request #50284 · rails/rails - これまで
belongs_to :user, inverse_of: :posts
のように、belongs_toの反対側の関連であるhas_manyはデフォルトの命名でも明示的にinverse_ofの指定が必要だったが不要になる - 非互換な変更なので設定で変更できるようになった
- 最初は7.2でデフォルトtrueの設定だったのだけど関連するIssueが複数発生したのでデフォルトfalseになった
- Don't enable automatically_invert_plural_associations by default by rafaelfranca · Pull Request #51896 · rails/rails
- 関連スライド
- Add row_count field to sql.active_record notification by marvin-bitterlich · Pull Request #50887 · rails/rails
- Active Support Instrumentation — Ruby on Rails Guides
sql.active_record
にrow_countが追加されている
- このPRを作った人の所属している会社(intercom)ではたくさんの行を返すクエリを検出している模様
- DBレベルで100K行より多いクエリをブロックするようにしているらしい
- そうそうないと思うけど、たくさんの行を返したら通知するような仕組みがあるとまずいクエリに気づきやすくなって良さそうですね
- Support
:source_location
tag option for query log tags by fatkodima · Pull Request #50969 · rails/rails - query_log_tagsはクエリ実行時のコメントにどのコントローラやアクションから実行されたやつなのかをコメントで追記してくれるやつ
- 前は basecamp/marginalia: Attach comments to ActiveRecord's SQL queries というgemだった
- marginaliaにはクエリを実行した場所を出力する機能はあったのだけど、query_log_tagsに移行する際にその機能は入らなかった
- "実行した場所を表示する"のが結構重たい処理だったというのが理由の一つ
- これまではcallerとかcaller_locationというメソッドを利用して呼び出し元のスタックトレースを全部取得する必要があった
- Ruby3.2からeach_caller_locationというメソッドが追加されて、これを利用することで処理の重たさを軽減できるようになった
- Thread#each_caller_location
- 一つずつバックトレースをイテレーションしていくので、途中でお目当てのものが見つかったらbreakできる
- これにより導入してもいいんじゃない?となったみたい
- オプションとして
config.active_record.query_log_tags << :source_location
のような設定をすると有効になる - 軽減したとはいえまだ重たいので基本的には開発環境で使え、とのこと
- もしくは本番環境でデバッグしたいときだけオンにするとか
ActiveRecord::Relation#order
supports hash likeActiveRecord::Relation#where
by mylesboone · Pull Request #50000 · rails/rails- Allow
ActiveRecord::Base#pluck
to accept hash values by fatkodima · Pull Request #51565 · rails/rails - Allow ActiveRecord::QueryMethods#select to accept a hash by alextrueman · Pull Request #45612 · rails/rails
- 以下コード例。見ればわかる
Topic.includes(:posts).order(posts: { created_at: :desc })
# Before
Post.joins(:comments).pluck("posts.id", "comments.id", "comments.body")
# After
Post.joins(:comments).pluck(posts: [:id], comments: [:id, :body])
Post
.joins(:comments)
.select(
posts: { id: :post_id, title: :post_title },
comments: { id: :comment_id, body: :comment_body}
)
# instead of:
Post
.joins(:comments)
.select(
"posts.id as post_id, posts.title as post_title,
comments.id as comment_id, comments.body as comment_body"
)
- Add the ability to ignore counter cache columns while they are backfilling by fatkodima · Pull Request #51453 · rails/rails
- 既存のテーブルにcounter cacheを導入すると、counter cacheのカラムに現在の値を反映させる作業が必要になる
- sizeメソッドなどの一部の関連メソッドはcounter cacheが有効だとそのカラムの値を使うケースがあるので、現在の値を反映するまでは有効にできない
- カラム追加→rakeタスクなどで↓のように更新する→counter cacheをオンにする、というのがパッと思いつくけど、rakeタスクからcounter cacheをオンにするまでにcommentsの増減があったら…?
# 雑に考えた実装です
Post.in_batches do |relation|
relation.pluck(:id).each do |id|
Post.reset_counters(id, :comments)
end
end
- なのでこれまで正しくやりたい場合はCommentにコールバックを仕掛けるか、トリガを仕掛けるかくらいしか方法がなかった
- これからは次のように設定できる
class Comment < ApplicationRecord
belongs_to :post, counter_cache: { active: false }
end
- ↑のようにすると、sizeメソッドなどはcounter cacheのカラムは使わない
- しかし対象モデルの増減時にはcounter cacheのカラムを増減させる
- ので、rakeタスク実行で問題なくなるのだった
- Add support for recursive CTEs in ActiveRecord by ClearlyClaire · Pull Request #51601 · rails/rails
- Rails7.1でCTE自体はサポートされたけど再帰的CTEは未サポートだった
Post.with_recursive(
post_and_replies: [
Post.where(id: 42),
Post.joins('JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id'),
]
)
WITH RECURSIVE "post_and_replies" AS (
(SELECT "posts".* FROM "posts" WHERE "posts"."id" = 42)
UNION ALL
(SELECT "posts".* FROM "posts" JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id)
)
SELECT "posts".* FROM "posts"
- SQLアンチパターンで記載されているナイーブツリーは、これからはアンチパターンではなくなるかもしれないですね
- とはいえ経路列挙モデルなどのほうが適しているケースが有る(SELECT時の速度面)のでケースバイケースで一番いいのを選ぶ、というのは変わらない
- Support
touch_all
in batches by fatkodima · Pull Request #51785 · rails/rails - もともとupdate_allやdelete_allでできていたのだけど、touch_allはできていなかった
- 次のようにかけるようになったぞ
Post.in_batches.touch_all
- Expose
assert_queries
andassert_no_queries
assertions by p8 · Pull Request #50281 · rails/rails - これまでRails内部のテストで使われていたヘルパだったんだけど、アプリケーションのテストでも便利じゃん?ということでpublicになった模様
- べんり
class ArticleTest < ActiveSupport::TestCase
test "queries are made" do
assert_queries(1) { Article.first }
end
end
次のようなテストケースがある
test "see proc output" do
assert_difference -> { 1 } do
end
end
do...end
を実行したあとに-> { 1 }
に変化があることを確認しているが、当然-> { 1 }は毎回1を返すので失敗する。このときこれまではこのようなメッセージで失敗していた
#<Proc:0x000074357846eae8 /home/richard/my-test-app/test/models/test.rb:21 (lambda)> didn't change by 1, but by 0.
Expected: 2
Actual: 1
これがこうなる
"-> { 1 }" didn't change by 1, but by 0.
Expected: 2
Actual: 1
RubyVM::AbstractSyntaxTree.of
を利用してブロック部分のソースコードを得ているRubyVM::AbstractSyntaxTree.of
をサポートしていない処理系(ex: JRuby)だったり、ブロックが複数行だったりしたら以前と同じふるまいにしている- RSpecのchangeも同等の機能があるよう
- Add ENV["SKIP_TEST_DATABASE_TRUNCATE"] flag to speed up multi-process test runs by dhh · Pull Request #51686 · rails/rails
- Rails標準のテストで、かつ並列実行しているときの振る舞いを変えたという話
- HEYだと24プロセスでそれぞれ178テーブルをTRUNCATEしていた
24*178=4272
- それをやめたら最大10秒テスト実行時間が短縮されたとのこと
- テストのデータが全部トランザクションの中で作られているならTRUNCATEは不要
- もちろんトランザクション外でマスターデータなどを入れていたら、テスト実行前に一回消しておくのが安全ですね
- Allow assertionless tests to be reported by fatkodima · Pull Request #51625 · rails/rails
- Remove configuration to control what we do with tests without assertions · rails/rails@6a6c7e6
- Catching Assertionless Tests | Rails at Scale からのインスパイアとのこと
- assertionがないテストを実行するとwarningが出るようになる
- 対象はRails標準のテストだけ 😭
- rubocopで、RSpecでも使えるcopがある
- RSpec :: RuboCop Docs
- けど、これは静的解析なので、↑のshopifyのブログエントリにあるような、条件によってassertionを通らないことがあるようなケースは検出できないはず…
- そもそもRSpecには実行したassertionの数を検出する機構がない、というのが問題なので↓が解決するといいなあ