ActiveRecordでサブクエリのJOIN


たとえば

「ユーザー1がコメントした記事の中で、コメントのlike数が1以上の記事を検索する」

SELECT `posts`.* 
FROM `posts` 
  INNER JOIN (
    SELECT `comments`.* 
    FROM `comments`
    WHERE 
      `comments`.`user_id` = 1 AND
      `comments`.`likes_count` >= 1
  ) liked_user_comments ON liked_user_comments.`post_id` = `posts`.`id`

こんなのをRailsで書くには?

コード

Arel::Table オブジェクトの取得

user = User.find(1)
posts = Post.arel_table
comments = Comment.arel_table 

サブクエリの組み立て

.project などによってできる Arel::SelectManagerオブジェクト の .as を呼ぶと Arel::Nodes::TableAliasオブジェクト を得ることができ、テーブルと同様に扱うことができるようになります。

liked_user_comments = comments
                          .project(Arel.sql('*'))
                          .where(comments[:user_id]
                              .eq(user.id)
                              .and(comments[:likes_count].gtdq(1)))
                          .as('liked_user_comments') # これ

JOIN句の組み立て

先ほど得た Arel::Nodes::TableAlias を .join に渡すとサブクエリになります。
最後に .join_sources を呼ぶことでJOINの右辺を取り出すことができます。(Arel::Nodes::InnerJoinオブジェクト)

join_conds = posts
                 .join(liked_user_comments, Arel::Nodes::InnerJoin)
                 .on(liked_user_comments[:post_id].eq(posts[:id]))
                 .join_sources

ActiveRecord の joins に渡す

Post.joins(join_conds)

参考