【ActiveRecord】3つ以上のテーブルをJOINしたい
状況
3つ以上のテーブルをJOINしたい。
想定するモデル
記事、段落、文といったモデルを考えてみる。記事は複数の段落を含む。段落は複数の文を含む。以下のモデルがあるものとする。
# articles has_many :paragraphs # paragraphs belongs_to :article has_many :sentences # sentences belongs_to :paragraph
書き方
A) 特定のsentenceが所属するarticleを取得する(孫から先祖へ)。
Sentence.eager_load(paragraph: :article).where(sentences: {id: ?})
B) 特定のarticleに含まれるsentencesを取得する(先祖から孫へ)。
Article.eager_load(paragraphs: :sentences) .where(articles: { id: ? })
where
にはテーブル名につづけてハッシュでカラムと値を指定する。テーブル名を渡さないとエラーになる。
OK
.where( sentences: { id: ? })
NG
.where( id: ? )
どのテーブルのidなのかがわからないからダメ。
SQLなら
上記コードから以下のクエリが生成される。
A)
SELECT * FROM sentences LEFT OUTER JOIN paragraphs ON paragraphs.id = sentences.paragraph_id LEFT OUTER JOIN articles ON paragraphs.article_id = articles.id WHERE sentence.id = ?
B)
SELECT * FROM articles LEFT OUTER JOIN paragraphs ON articles.id = paragraphs.article_id LEFT OUTER JOIN sentences ON paragraphs.id = sentences.paragraph_id WHERE sentence.id = ?
モデル名、単数/複数?
複数形または単数形のどちらにするのかはモデルの関連付けに従う。
たとえば上記 B)の場合、Articleは複数のParagraphsをもつので、eager_loadの第一引数は複数形のparagraphs
とする。同じように、paragraphsは複数のsentencesをもつので、第二引数も複数形のsentences
とする。
もっとJOINしたい
たとえば記事の筆者も表示したくなったとする。そのためにauthors
モデルを追加でJOINしたい。このとき、任意の著者によるsentence、paragraph、articleは次のようにして取得できる。
Sentence.eager_load(paragraph: {article: :author}) .where(authors: {name: ?})
モデル名をハッシュでネストすればOK。
(paragraph: {article: :author})
雑感
Railsガイドに複数モデルの関連付けの方法が紹介されている。読んでもわからなかったので、色々ググって3つ以上のテーブルをつなぐ記法を調べる必要があった。
Active Record クエリインターフェイス | Rails ガイド
Active Recordの記法がわからない場合、生SQLを食わすという手段もある。
https://railsguides.jp/active_record_querying.html#sql%E3%81%A7%E6%A4%9C%E7%B4%A2%E3%81%99%E3%82%8B
find_by_sqlに生クエリを渡して実行するやり方についてメモした。