💎

【ActiveRecord】結合したテーブルに同名カラムがある場合 select('*') を使用すると違うモデルにマッピングされる

2022/06/22に公開

はじめまして、おてつたび の川尻です。
去年見つけて投稿したIssueがPR作成されて進んでいたので、紹介します。

そもそもの Issue はこちら。(タイトル名が合ってない気もしますが)
https://github.com/rails/rails/issues/41151

Issue にも記載した例で解説します。

著者(authors)と記事(articles)テーブルがあり、author_id で紐づいているとします。
モデルもそれぞれ AuthorArticle があります。

authors

id name
1 John
2 Smith

articles

id author_id name
1 2 Hello World

この時結合して select('*') を使用すると、Articleauthors のレコードの値がアサインされます。
外部結合(left_joins, eager_load)も同じ結果になります。

article = Article.joins(:author).select('*').find 1
# => SELECT * FROM "articles" INNER JOIN "authors" ON "authors"."id" = "articles"."author_id" WHERE "articles"."id" = 1 LIMIT 1

article.id
# => 2
# 1 が正しい

article.name
# => Smith
# Hello World が正しい

テーブル名を付けると正しくマッピングされます。
Article.joins(:author).select('articles.*', 'authors.*')

つまり、テーブル名が無いと同名カラムをどのテーブルのモデルにマッピングするか Arel が判断できないので間違ったモデルにアサインされてしまうのです。
回避も SELECT * が原因なので、テーブル名をつけて「*」を使用する(select('articles.*))、もしくはカラムを指定する(select(:id, :name))になります。

今後

結合したテーブルと同名カラムがある場合はWarningメッセージを出力するように対応してくれています。
「*」だけで指定するケースはあまり無いと思いますが、使用する際はご注意ください!
https://github.com/rails/rails/pull/44236

終わりに

株式会社おてつたびでは、一緒に働く仲間を募集しています!
WebとiOSエンジニアを募集しております。
募集が見つからない場合は直接ご連絡していただいても大丈夫です!

https://otetsutabi.notion.site/otetsutabi/7312d1ad95d043a7a824752a389dc111
https://www.wantedly.com/companies/company_1381802
https://meety.net/matches/ehIndfduAZJe

Discussion