⚠️

has_many :through を使うとき、経由する中間テーブルとの関連付けを定義し忘れるとエラーになるよ

に公開

対象読者: Rails入門者

has_many :through を使用する際、経由する中間テーブルとの直接的な関連付けを定義し忘れるとエラーが発生します。
これは、Railsが発行するSQLの組み立てに必要な情報が不足するためです。


1. よくある定義漏れのパターン

問題のあるモデル定義例:

# app/models/user.rb
class User < ApplicationRecord
  # NG: 中間テーブル(registrations)との直接の関連定義がない
  has_many :events, through: :registrations
end

# app/models/registration.rb
class Registration < ApplicationRecord
  belongs_to :user
  belongs_to :event
end

# app/models/event.rb
class Event < ApplicationRecord
  has_many :registrations
  has_many :participants, through: :registrations, source: :user
end

上記の User モデルでは、has_many :events, through: :registrations と記述されていますが、その手前に has_many :registrations という、User モデルと中間テーブルである registrations との直接の関連定義が存在しません。


2. SQL発行における影響

User.first.events のようにデータを取得しようとすると、Railsは users テーブル、registrations テーブル、events テーブルをJOINするSQLを生成しようとします。

具体的には、まず users テーブルと registrations テーブルをJOINし、次にその結果と events テーブルをJOINする必要があります。

しかし、User モデルに has_many :registrations の定義がない場合、Railsは users テーブルと registrations テーブルをどの外部キー(例: registrations.user_id)でJOINすればよいかの情報を解決できません。その結果、適切なSQLを組み立てられず、エラーとなります。


3. 正しいモデル定義

中間テーブルとの関連を明示的に定義する必要があります。

# app/models/user.rb
class User < ApplicationRecord
  has_many :registrations # 中間テーブルとの関連を定義
  has_many :events, through: :registrations
end

この has_many :registrations の定義により、Railsは users テーブルの id カラムと registrations テーブルの user_id カラムを紐付けてJOINすることを理解できます。


4. 正しい定義の場合に発行されるSQL(イメージ)

User.find(1).events を実行した場合、以下のようなSQLが発行されます。

SELECT "events".*
FROM "events"
INNER JOIN "registrations" ON "events"."id" = "registrations"."event_id"
WHERE "registrations"."user_id" = 1;

このSQLが正しく機能するためには、まずRailsが users テーブルと registrations テーブルの関連(この場合は registrations.user_id = users.id)を認識していることが前提となります。そのために has_many :registrations が必要なのです。


結論

has_many :through を使用する際は、以下の点を必ず確認する。

  • through: で指定する関連名(例: :registrations)に対する has_many 定義(例: has_many :registrations)が、起点となるモデルに存在すること。

Discussion