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