- Ruby / Rails関連
週刊Railsウォッチ: 複合主キーをスキーマから導出可能に、 DBアダプタの例外をconnection_poolに保存ほか(20230628前編)
こんにちは、hachi8833です。
🔗Rails: 先週の改修(Rails公式ニュースより)
つっつきボイス:「Rails 7.1.0のマイルストーンを見てみると現在13件で前より少し増えてました↓」「経験上Railsのマイルストーンは厳密に運用されているわけではないので、次にどんな機能が入るかという目安ぐらいに考えるのがいいと思います」「issueに締切日も設定されていませんし、そうですね」
「なお、7.1.0マイルストーンの常連だったRackのバージョン3以上必須化対応は一通り機能が揃ったようなのでissueがクローズされていました↓」
参考: Allow rack >= 3
in Rails. by ioquatix · Pull Request #46594 · rails/rails
🔗 with_routing
ヘルパーをクラスレベルで呼び出せるようになった
with_routing
ヘルパーをクラスレベルで呼び出し可能になった。クラスレベルで呼び出された場合、以下のようにルーティングがテストごとに設定され、テスト後にリセットされる。class RoutingTest < ActionController::TestCase with_routing do |routes| routes.draw do resources :articles resources :authors end end def test_articles_route assert_routing("/articles", controller: "articles", action: "index") end def test_authors_route assert_routing("/authors", controller: "authors", action: "index") end end
Andrew Novoselac
同Changelogより
つっつきボイス:「with_routing
というルーティング用テストヘルパーがあったとは」「ルーティングの設定を一時的に追加してテストしたいときに使えるようですね」「複数のテストで使えるようにクラスレベルでも設定できるようになったらしい」「ルーティングのテストって普段あまり書かないせいか使い道はすぐには思いつかないかな」「Railsエンジン的なものやルーティング周りの定義自体を追加するようなgem(Deviseのdevise_scopeなど)で、ルーティングの拡張機能部分をテストするときに便利かも👍」
参考: with_routing
-- ActionDispatch::Assertions::RoutingAssertions
動機/背景
このプルリクを作成した理由は、with_routes
でセットアップしたルーティングを複数のテストケースで使っているファイルがあるため。テストファイル全体でルーティングをもっと楽にセットアップできるヘルパーが欲しい。詳細
with_routes
の既存の実装を2つのprivateメソッドに切り出した(ルーティング作成用とリセット用)。そしてsetup
ブロックでルーティングを作成し、teardown
ブロックでリセットするクラスメソッドを定義した。テストケースでこのクラスメソッドを呼び出せるように、ActionDispatch::Assertions
とActionDispatch::Assertions::RoutingAssertions
をconcernsに変換した。
同PRより
🔗 複合主キー関連
🔗 スキーマから複合主キーを導出可能になった
複合主キーを含むスキーマを持つアプリケーションを起動したときにwarningが発生しないようになり、
ActiveRecord::Base#primary_key
の値がnil
になることもなくなる。
以下のようなtravel_routes
テーブル定義とTravelRoute
モデルがあるとする。create_table :travel_routes, primary_key: [:origin, :destination], force: true do |t| t.string :origin t.string :destination end class TravelRoute < ActiveRecord::Base; end
この
TravelRoute.primary_key
の値は自動的に["origin", "destination"]
に導出される。
Nikita Vasilevsky
同Changelogより
つっつきボイス:「ActiveRecord::Base#primary_key
がnil
になったらたしかに問題」「この改修でprimary_key
属性が配列になるということは、これが 文字列を返す前提のコードが動かなくなるんじゃないかな」「あっそうか」
「ところでprimary_key
属性は単数形のままなんですね」「スキーマキャッシュのprimary_keys
メソッドは前から複数形ですけどね↓」
# activerecord/lib/active_record/attribute_methods/primary_key.rb#129
def get_primary_key(base_name) # :nodoc:
if base_name && primary_key_prefix_type == :table_name
base_name.foreign_key(false)
elsif base_name && primary_key_prefix_type == :table_name_with_underscore
base_name.foreign_key
else
if ActiveRecord::Base != self && table_exists?
- pk = connection.schema_cache.primary_keys(table_name)
- suppress_composite_primary_key(pk)
+ connection.schema_cache.primary_keys(table_name)
else
"id"
end
end
end
参考: Rails API primary_key
-- ActiveRecord::AttributeMethods::PrimaryKey::ClassMethods
参考: Rails API primary_keys
-- ActiveRecord::ConnectionAdapters::SchemaCache
このプルリクは、"Active Record does not support composite primary key" warningを止めて
ActiveRecord::Base#primary_key
をArray
として導出可能にする。
warningがなくなったので、これを期待する/期待しないテストを削除した。
同PRより
🔗 複合主キーを持つテーブルのfind_each
、find_in_batches
、in_batches
で:asc
、:desc
を指定可能になった
find_each
、find_in_batches
、in_batches
で複数カラムによる順序付けをサポート
複合主キーがあるテーブルでfind_each
、find_in_batches
、in_batches
を実行するときに、キーごとに:asc
や:desc
を指定可能になる。Person.find_each(order: [:desc, :asc]) do |person| person.party_all_night! end
Takuya Kurimoto
同Changelogより
つっつきボイス:「バッチ系メソッドでも複合主キーでorder: [:desc, :asc]
のようにキーごとに:asc
やdesc
を指定できるようになった: 複合主キー対応したものが増えてきましたね👍」
- Rails API
find_each
--ActiveRecord::Batches
- Rails API
find_in_batches
--ActiveRecord::Batches
- Rails API
in_batches
--ActiveRecord::Batches
🔗 データベース関連の例外をconnection_pool
に保存するようになった
- PR: Store
connection_pool
in database-related exceptions by luanzeba · Pull Request #48295 · rails/rails
動機/背景
GitHubでデータベース関連のエラーを調査するたびに、クラスタやロールといったコネクション関連の情報をさらに深掘りすることになる。
このプルリクの目的は、アダプタから発生した例外をそれに関連するconnection_pool
に保存することで、アプリケーションがそこに手軽にアクセスして例外トラッキングシステムに送信可能にすること。
Railsがマルチプルデータベースをサポートしていることを考えれば、適切な機能だと思う。アダプタから例外が発生した場合は常に[1]コネクションプールを提供して、アプリケーションの問題点を掘り下げながらデバッグ可能にする。これは、マルチプルデータベースのRailsアプリケーションを実行する際に重要な機能。
私たちはコネクションやロール、シャードなどの関連コンテキストをconnection_pool
で提供する方法を選択した。コネクションを直接を提供する方法は、プールに制御が戻った後で別のスレッドに渡されて誤用される可能性があるため、避けたかった。
ConnectionAdapters::PoolConfig
を使ってもよかったのだが、:nodoc:
につき採用しなかった。
脚注[1]: 1つ例外的な場合を見つけた。SQlite3のアダプタは、データベースファイルを指定しないとArgumentError
を発生する。この例外をコネクションプールに含める形で変更するのは不適切だと考える。rails/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb at 02df5fe1e77c38f8086ec45e349ba3d1b25605a0 · rails/rails
同PRより
つっつきボイス:「これはGitHubからのプルリクです」「アダプタレベルの例外をconnection_pool
に保存することで例外にアクセスしやすくしたんですね」「改修ではTrilogy以外にPostgreSQLやMySQLやSQLite3のアダプタも対応しているのね」
「データベース関連の例外を解明するのはたいてい難しいし、GitHubは例のTrilogy(MySQL互換のDBクライアント)を使っていたりするので(ウォッチ20230502)こういう例外を追いやすくする機能が切実なのかもしれませんね」「そうそう、TrilogyはGitHubが作ったんでしたね」
🔗 エンジンのdraw_paths
をアプリのルーティングに追加するようになった
エンジンの
draw_paths
をアプリケーションのルーティングに追加し、エンジンのパスで定義されているルーティングファイルをアプリケーションで処理できるようになった。
Gannon McGibbon
同Changelogより
参考: Rails API draw
-- ActionDispatch::Routing::Mapper::Resources
つっつきボイス:「draw
メソッドって使ったことなかった気がするけど、別のルーティングファイルを読み込むメソッドなのか、なるほど」「今までは自動ではできなかったんですね」「改修は1行追加だけ↓」「draw_paths
はエンジン側で読み込ませるんですね」
# railties/lib/rails/engine.rb#L585
initializer :add_routing_paths do |app|
routing_paths = paths["config/routes.rb"].existent
external_paths = self.paths["config/routes"].paths
routes.draw_paths.concat(external_paths)
+ app.routes.draw_paths.concat(external_paths)
if routes? || routing_paths.any?
app.routes_reloader.paths.unshift(*routing_paths)
app.routes_reloader.route_sets << routes
app.routes_reloader.external_routes.unshift(*external_paths)
end
end
参考: Railsのルーティングをdraw
を使ってまとめる - Qiita
🔗 引用符を含むパラメータでのMIMEタイプの扱いを改善した
Mime::Type
がパラメータのMIMEタイプをサポートし、引用符を正しく扱えるようになった。
Accept
ヘッダーの解析時にq
値パラメータより前のパラメータが保持され、一致するMIMEタイプが存在する場合に利用される。
現行の機能を維持するために、パラメータなしでメディアタイプを検索するフォールバックが作成されている。この変更によって、たとえばJSON APIの
application/vnd.api+json; profile="https://jsonapi.org/profiles/ethanresnick/cursor-pagination/" ext="https://jsonapi.org/ext/atomic"
のような複雑なカスタムMIMEタイプを利用できるようになる。Nicolas Erni
同Changelogより
動機/背景
このプルリクを作成した理由は、引用符で囲まれたパラメータをMime::Type
が正しく扱えないため。ここには、カンマなどほとんどの文字が含まれる可能性がある。さらに現在の実装では、MIMEタイプを読み込むときにはこのパラメータを無視するにもかかわらず、登録時には無視しない。修正: #48052
詳細
このプルリクは、
Mime::Type
を変更して、引用符で囲まれたパラメータ値内に(RFC 822 3.3で定義されている\r
、\
、"
以外の)あらゆる文字を含められるようにする(エスケープされた文字を除く: 現在の制約を参照)。さらに、
Accept
ヘッダーの他の部分は単純に,
で区切られるわけではないが、引用符内で配慮するようになった。現在の実装では、
Mime::Type.register
はパラメータを保持するが、一方でMime::Type.parse
はパラメータを削除している。このプルリクでは、すべてのパラメータの前にあるq
値パラメーターを(RFC 7231 5.3.2の定義に沿って)保持する。
検索中にパラメータにMIMEタイプが見つからなかった場合は、フォールバックとしてパラメータなしでMIMEタイプを検索する。これはmultipart/form-data; boundary="simple boundary"
のような場合に有用であり、かつ現在の振る舞いを変えない。現在の制約
現時点では以下の制約が存在するが、どこまで対応する価値があるか自分にはわからない。
- 引用符で囲まれた文字列に
\"
のようなエスケープ済み文字が含まれる可能性がある。自分の実装では、解析をシンプルにして効率を落とさないためにバックスラッシュを許していない。- パラメータの前後にホワイトスペースの違いがある場合、マッチしているとみなされない可能性がある。例:
my/type;test="value"
はmy/type; test="value"
とマッチしない。- RFC 7231 5.3.2によると、パラメータにあるMIMEタイプはパラメータにないMIMEタイプより優先される。しかしこの情報に沿って扱おうとすると、MIMEタイプをパラメータから切り離す必要があり、計算量が増えてしまう。
同PRより
参考: Rails API Mime::Type
つっつきボイス:「たとえばboundary="simple, boundary"
みたいにカンマ区切りを引用符で囲んだものも渡せるようになったんですね」「boundary="simple, boundary", text/xml
みたいに引用符の外にもカンマがあったりすると面倒になりそうだけど、そういうのも扱えるのか」「multipart/form-data
にboundary
を指定したりするのはよくありますね」
#actionpack/test/dispatch/mime_type_test.rb#83
test "parse arbitrary media type parameters with comma" do
accept = 'multipart/form-data; boundary="simple, boundary"'
expect = [Mime[:multipart_form]]
assert_equal expect, Mime::Type.parse(accept)
end
test "parse arbitrary media type parameters with comma and additional media type" do
accept = 'multipart/form-data; boundary="simple, boundary", text/xml'
expect = [Mime[:multipart_form], Mime[:xml]]
assert_equal expect, Mime::Type.parse(accept)
end
参考: Accept - HTTP | MDN
参考: MIME type (MIMEタイプ) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN
前編は以上です。
バックナンバー(2023年度第2四半期)
週刊Railsウォッチ: 書籍『The Rails and Hotwire Codex』、JavaScript Primer改訂2版ほか(20230622後編)
- 20230621前編 Action ViewのサニタイザがHTML Living Standard(旧HTML5)準拠にほか
- 20230614後編 RailsでApplication Layer Encryption、rubocop-factory_bot登場ほか
- 20230613前編 Arel::Nodes::Cteが追加、html_escape_onceの修正ほか
- 20230608後編 Jets v4リリース、頑張らない型導入、Rust言語からCrabがforkほか
- 20230607前編 MessagePackがcookieシリアライザとメッセージシリアライザにも導入ほか
- 20230531後編Rubyで環境変数を扱う、Web標準に「Baseline」ステータス追加ほか
- 20230525後編 Ruby 3.3.0-preview1リリース、in_order_ofのバグ修正ほか
- 20230524前編 withで作成したリレーションをjoinsで指定可能に、キャッシュストアの例外処理を統一ほか
- 20230502 スライド『Rails 7.1をn倍速くした話』、Rails 7.1でMessagePackをサポートほか
- 20230427後編 第1回Rails Worldが10月に開催、『研鑽Rubyプログラミング』でRuby本体も高速化ほか
- 20230425前編 Rails 7.1の複合主キー対応が引き続き進む、exceptメソッドにwithoutエイリアスが追加ほか
- 20230413後編 ShopifyのRubyパーサーyarp、RJITを書いた理由ほか
- 20230412前編 複合主キーの実装が進む、Rails公式のバグ再現用テンプレートほか
- 20230406後編 Rubyオブジェクトモデルクイズの最難問ほか
- 20230405前編 Arel::Nodes::NodeにAPIドキュメントが追加、rubocop-mdほか
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)