技術メモ

書いておぼえるブログ

【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に生クエリを渡して実行するやり方についてメモした。

gijutsumemo.hatenadiary.jp