- Ruby / Rails関連
週刊Railsウォッチ: Rails 8マイルストーン、2023年のRails振り返り、Solid Queueほか(20240117前編)
こんにちは、hachi8833です。
🔗Rails: 先週の改修(Rails公式ニュースより)
本家が先に進んでいるので取り戻しにかかります。
🔗 assert_queries
とassert_no_queries
がActiveSupport::TestCase
で利用可能になった
- PR: Expose
assert_queries
andassert_no_queries
assertions by p8 · Pull Request #50281 · rails/rails
動機/背景
作成されたクエリ件数が期待通りであるというアサーションのため、Rails内部では
assert_queries
やassert_no_queries
が使われている。これらのアサーションはアプリケーション側でも有用だろう。これらのアサーションをモジュールに切り出すことで、必要に応じて
include
できるようになる。
Active Recordが定義されると、ActiveSupport::TestCase
にこれらのアサーションが追加される。Active Storage、Action View、Action Textでも、実装を重複させずにこのモジュールを使うようになった。
Active Recordのテストで内部的に使われるActiveRecord::TestCase
もこれらのアサーションを実装した。ただし、これらは少し高度かつ込み入っており、SQLCounterクラスを使っている。とりあえず話を複雑にしないため、この実装は使っていない。追加情報
これらのアサーションは
ActiveSupport::TestCase
に追加される。class ArticleTest < ActiveSupport::TestCase test "queries are made" do assert_queries(1) { Article.first } end end
同PRより
つっつきボイス:「assert_queries
で実際に実行されたSQLクエリの件数をチェックできるのね: N+1問題を手軽にチェックしたりするのに使えるかも」「assert_no_queries
はクエリ発行件数ゼロ専用のアサーションなんですね」
# activerecord/test/cases/assertions/query_assertions_test.rb#L12
def test_assert_queries
assert_queries(1) { Post.first }
error = assert_raises(Minitest::Assertion) {
assert_queries(2) { Post.first }
}
assert_match(/1 instead of 2 queries/, error.message)
error = assert_raises(Minitest::Assertion) {
assert_queries(0) { Post.first }
}
assert_match(/1 instead of 0 queries/, error.message)
end
end
def test_assert_no_queries
assert_no_queries { Post.none }
error = assert_raises(Minitest::Assertion) {
assert_no_queries { Post.first }
}
assert_match(/1 .* instead of 2/, error.message)
end
🔗 image/svg+xml
が圧縮可能Content-Typeに追加された
image/svg+xml
がActionDispatch::Static
の圧縮可能Content-Typeに追加された。Georg Ledermann
同Changelogより
動機/背景
このプルリクは、
ActionDispatch::Static
の圧縮可能Content-Typeにimage/svg+xml
を追加する。(なお、これは@ledermannによる#42407をrebaseして再送信したものであり、2021年に@zzakがapprove済み)
詳細
元の説明文:
ActionDispatch::Static
の圧縮可能Content-Typeにimage/svg+xml
を追加することでGZIPやBrotliを利用可能にする。同PRより
つっつきボイス:「image/svg+xml
というContent-Typeが圧縮可能になったんですね」「テスト用にRailsロゴのSVG画像も追加されてる」「お、fixturesの下に公共/gzip
というディレクトリがありますね↓」「日本語だ」「意図があるとすれば、ディレクトリ名のエンコードが非ASCIIでも正常に動作するかどうかをチェックするとかかな」「それありそうですね」
後で調べると、2013年のコミットでfixtureに日本語のディレクトリ名やファイル名が追加されていました↓。
参考: Fix Encoding::CompatibilityError when public path is UTF-8 · rails/rails@436ed51
🔗 TrilogyAdapterでDATABASE_URL
にUNIXソケットを指定可能になった
- PR: TrilogyAdapter: ignore host if socket is set by casperisfine · Pull Request #50349 · rails/rails
TrilogyAdapterで
socket
パラメータが設定済みの場合はhost
を無視するようになった。これにより、
DATABASE_URL
経由でUNIXソケット上にコネクションを設定可能になる。DATABASE_URL=trilogy://does-not-matter/my_db_production?socket=/var/run/mysql.sock
Jean Boussier
同Changelogより
つっつきボイス:「?socket=/var/run/mysql.sock
というクエリをDATABASE_URL
で指定可能になったんですね」「ソケットを使うならURLのhost部に何があっても無関係でしょうね」
🔗 Action MailboxとAction Textでもモデルのテーブル名にActive Recordのプレフィックス/サフィックス設定が効くよう修正
- PR: Take AR affixes into account for Action Mailbox database models by chaadow · Pull Request #50300 · rails/rails
- PR: Take AR affixes into account for Action Text database models by chaadow · Pull Request #50299 · rails/rails
つっつきボイス:「昨年末に#50167でActive Recordのプレフィックス/サフィックス設定がActive Storageモデルのテーブル名で効くように修正されていましたね(ウォッチ20231222)」「上の2つはAction MailboxとAction Textについても同じ修正を行った、なるほど」
🔗 Railsのランナーに--skip-executor
オプションが追加された
- PR: Add runner script option to disable Executor wrap by bensheldon · Pull Request #50223 · rails/rails
bin/rails runner --skip-executor
オプションを指定することで、ランナースクリプトをExecutorでラップしないようにできるようになった。Ben Sheldon
同Changelogより
動機/背景
#44999でRailsのランナースクリプトがRailsのExecutorでラップされるようになった。
これは素晴らしいことだが、実行に時間のかかるスクリプトやループするスクリプトやデーモン的なスクリプトではそうではない。そのような場合は、スクリプト内で個別の作業単位をExecutorで意図的にラップできるようにする方がよさそう。
詳細
このプルリクは、Railsスクリプトランナーに
--skip-executor
を渡せるようにすることで、Executorのラップを条件付きでスクリプトに追加「しない」ようにする。別のオプション: Executorを削除する代わりに、#43550でExecutorに導入されたリエントラント可能な
#perform
でランナーをラップすべきかもしれない。同PRより
つっつきボイス:「#44999はRails 7.1でマージされてる」「ランナースクリプトを自動的にExecutor
でラップしたのは、エラーレポートやクエリキャッシュが使えるようにするためだったのね: たしかに場合によってはExecutor
でラップしたくないときもありそう」
参考: Rails API ActionDispatch::Executor
参考: §2.5 bin/rails runner
-- コマンドラインツール - Railsガイド
🔗 SQLite3アダプタに生成カラム(generated columns)のサポートを追加
生成カラムは(storedおよびdynamicの両方について)SQLite 3.31.0以降でサポートされている。
このプルリクは、SQLite3アダプタにそうしたサポートを追加する。create_table :users do |t| t.string :name t.virtual :name_upper, type: :string, as: 'UPPER(name)' t.virtual :name_lower, type: :string, as: 'LOWER(name)', stored: true end
Stephen Margheim
同Changelogより
つっつきボイス:「SQLiteでも2020年から生成カラムが使えるようになってたとは知らなかった」「SQLiteのサポートがまた強くなった」
参考: 第150回 Generated Columnを利用してみる | gihyo.jp
参考: Generated Columns -- SQLite Documents
🔗 ActionController::Live#send_stream
にinstrumentationが追加された
ActionController::Live#send_stream
用のinstrumentationを追加。
send_stream
イベントへのサブスクライブが可能になる。このイベントのペイロードにはfilename
、disposition
、type
が含まれる。Hannah Ramadan
同Changelogより
つっつきボイス:「これはシンプルなinstrumentation APIの追加ですね」
参考: Active Support Instrumentation で計測 - Railsガイド
🔗 MySQLでArelのnulls_first
とnulls_last
が使えるようになった
MySQL向けに
nulls_last
を追加し、desc.nulls_first
を修正した。Tristan Fellows
同Changelogより
動機/背景
修正: #50078
このプルリクを作成した理由は、MySQLデータベースで、あるASCカラムを「null last」でソートする必要が生じたが、そのためにSQLフラグメントの利用を避けたかったため。ArelはMySQLを除いてこの機能をサポートしていることを発見した。MySQLの実装を調べてみたところ、実装が一貫していないことに気づいた。
元々は動作を一貫させるだけのつもりだったが、足りない機能も実装してみた。
詳細
このプルリクはMySQLデータベースにおける
.nulls_first()
と.nulls_last()
の実装を変更する。これにより、これらのメソッドが設計通りに動作するようになる。
同PRより
つっつきボイス:「Arelのdesc.nulls_first
がMySQLだと期待通り動いていなかったそうです」「悲しい」「ソートしたときにNULLを冒頭に配置するか末尾に配置するかを指定するメソッドなんですね」「お〜、.asc.nulls_last
や.asc.nulls_first.reverse
みたいな書き方ができるのか、よさそう😋」「これ今まで生SQLで書いてた」
# activerecord/test/cases/arel/visitors/mysql_test.rb#L156
describe "Nodes::Ordering" do
- it "should no-op ascending nulls first" do
it "should handle nulls first" do
test = Table.new(:users)[:first_name].asc.nulls_first
_(compile(test)).must_be_like %{
- "users"."first_name" ASC
+ "users"."first_name" IS NOT NULL, "users"."first_name" ASC
+ }
+ end
+
+ it "should handle nulls last" do
+ test = Table.new(:users)[:first_name].asc.nulls_last
+ _(compile(test)).must_be_like %{
+ "users"."first_name" IS NULL, "users"."first_name" ASC
+ }
+ end
+
+ it "should handle nulls first reversed" do
+ test = Table.new(:users)[:first_name].asc.nulls_first.reverse
+ _(compile(test)).must_be_like %{
+ "users"."first_name" IS NULL, "users"."first_name" DESC
+ }
+ end
+
+ it "should handle nulls last reversed" do
+ test = Table.new(:users)[:first_name].asc.nulls_last.reverse
+ _(compile(test)).must_be_like %{
+ "users"."first_name" IS NOT NULL, "users"."first_name" DESC
}
end
end
🔗Rails
🔗 2023年のRails振り返りとRails 8情報(Rails公式ニュースより)
つっつきボイス:「Rails公式が振り返る2023年と、Rails Foundation創立に関する記事です」「こうして見るとRails 7.1でいろんなものが追加されましたね」
以下は週刊Railsウォッチでの対応する見出しです。
ActionDispatch::AssumeSSL
ミドルウェアが追加された(ウォッチ20230214)- 複数ジョブを一度にエンキューする
perform_all_later
が追加(ウォッチ20230314) - Active Supportに
Object#with
が追加された(ウォッチ20230328) - Trilogyデータベースクライアント用のアダプタが導入される(ウォッチ20230502)
ActiveSupport::MessagePack
が追加(ウォッチ20230502)config.autoload_lib
を追加(ウォッチ20230725)- バックグラウンドジョブの
enqueue
呼び出し元をログ出力できるようになった(ウォッチ20230425) rails new
でBunを指定できるようになった(ウォッチ20230926)- minitestの行範囲を指定して実行可能になった(ウォッチ20230823)
- SQLite3アダプタのコネクション設定のパフォーマンスチューニング(ウォッチ20231004)
- Ruby 3.3以上でRailsアプリを実行するとYJITがデフォルトで有効になる(ウォッチ20231122)
ActiveRecord::Core#inspect
の出力をカスタマイズ可能になった(ウォッチ20231122)
「続いてRails 8のマイルストーンもできました↓」「お、ここをチェックしておけばRails 8の様子がある程度わかりますね」「後で読もう」
参考: 8.0.0 Milestone
なお、以下の記事でもRails 8を先行紹介しています。
参考: Sneak Peek on Rails 8 | lucas.dohmen.io(Ruby Weeklyより)
参考: A writer's Ruby(Ruby Weeklyより)
「上のDHHの記事によると、Rails 8でRubocopがデフォルトで入ってくるそうです」「このrubocop-rails-omakaseがそれか↓」「ついに入ってくるんですね」「なかなか大きな変更」「現代のRailsプロジェクトでRubocopを使わないことはまずないでしょうね」「このrubocop-rails-omakaseの設定が議論を呼びそうな気がする」
「あとBrakemanもRails 8でデフォルトになるそうです」「Brakemanは大好き❤️」
🔗 Solid Queue: 37signalsによるActive Job向けDBベースのジョブバックエンド(Ruby Weeklyより)
つっつきボイス:「37signalsが作ったSolid Queue、今のRails 8マイルストーンにもありますね」「Rails 8ではこれがデフォルトのジョブキューバックエンドになって(#50442)、PostgreSQLとMySQLとSQLite3を使えるらしい: ジョブキューのためにわざわざRedisサーバーを立てなくても済む方向を目指しているんでしょうね」「DBでのジョブキューで有用なSKIP LOCKED
がMySQL 8.0.1でも入っていたのね↓」
--同記事より
SELECT ... FOR UPDATE SKIP LOCKED
参考: MySQL :: MySQL 8.0.1: Using SKIP LOCKED and NOWAIT to handle hot rows
🔗 superglue: thoughtbotによるRails向けReact Reduxライブラリ(Ruby Weeklyより)
// 同記事より
import React from 'react'
import { useSelector } from 'react-redux'
import { Drawer, Header, Footer, ProductList, ProductFilter } from './components'
export default function FooBar({
header,
products = [],
productFilter,
rightDrawer,
footer
}) {
const flash = useSelector((state) => state.flash)
return (
<>
<p id="notice">{flash && flash.notice}</p>
<Header {...header}>
<Drawer {...rightDrawer} />
</Header>
<ProductList {...products}>
<ProductFilter {...productFilter} />
</ProductList>
<Footer {...footer} />
</>
)
}
つっつきボイス:「thoughtbotがRailsでReactを使うためのsuperglueというのを作ったそうです」「ドキュメントを眺めてみると、クライアントサイドルーティングはしないらしい↓」
「基本的にRailsのビューを使う形になっていて.jsを置くとReactページコンポーネントになり、.jsonを置くとデータだけを表示するのね↓」「PropsTemplateではjbuilderっぽい書式でDSLを書けるのか」
「どうなんだろう?管理画面なんかを作るにはいいのかもしれないけど」「近年フロントエンドを切り離してバックエンドと別に開発するようになってきたのは、フロントエンドのモデルとバックエンドのモデルが一致しないからという面もあるんですが、superglueのディレクトリ構成などを見ると両者が同じモデルを使うという前提になっているので、両者が密結合するわけですよね」「それもそうか」「その意味ではReactをフロントエンドフレームワークではなく、単なるビューとして使うことになるんじゃないかな: 少なくともフロントエンド指向の開発ではないと思います」
「superglueを入れてまでReactを使いたいかどうかですよね」「あるとすれば、Rails開発者がHotwireのStimulusとかを新たに勉強するよりは、既に知っているReactを使いたい場合とかかな🤔」
前編は以上です。
バックナンバー(2023年度第4四半期)
- 20231215後編 Rails 7.1で「Apple でサインイン」を実装、JetBrains AIほか
- 20231213前編 非推奨化済み機能をRails 7.2に向けて多数削除ほか
- 20231127後編 Ruby 3.3.0-preview3リリース、IRBのオートコンプリート強化ほか
- 20231122前編 Rails+Ruby 3.3でYJITがデフォルトでオンにほか
- 20231116後編 RBS 3.3.0とSteep 1.6.0リリース、Ruby Conf Taiwan 2023ほか
- 20231114前編 MariaDBのRETURNINGオプションをサポートほか
- 20231107 Kaigi on Rails発表「Simplicity on Rails」を見るほか
- 20231025後編 ShopifyのWebAssemblyツールチェインRuvyほか
- 20231024前編 7.1アップグレードガイドにActive Record暗号化設定の注意事項が追加ほか
- 20231018後編 Kaigi on Rails 2023関連イベント情報公開、複合主キーのlocality解説記事ほか
- 20231017前編 Active Storageのしくみを詳しく解説するDiscussion投稿ほか
- 20231011 Rails 7.1.0リリース、YARPがprismにリネームほか
- 20131004 Rails 7.1.0.rc1と7.1.0.rc2がリリース、SQLite3コンフィグの最適化ほか
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)