はじめに
テーブルの結合に対して苦手意識があり、せっかく便利なActiverecord
があるのにfind_by_sql
で逃げてしまい、sqlをゴリゴリに書いて結合させていた。
joins
を使って、3つ以上のテーブルを結合する方法に向き合ったのでまとめる。
結合するうえで、foreign_key
, primary_key
をどう設定していくのかを絡めて、例をとりあげる。
取り上げた例
「本」「レビュー」「レビューした人」の3つテーブルを扱う。
DBは下の画像のとおりに設定する。

取り出したい条件を
**「女性が評価4.0以上レビューした本のタイトル一覧」**とする。
つまり
where
句でgender = 'woman' かつ evaluation >= 4.0 を指定して、
select
をtitleとするイメージ。
ただ、これをどう結合して扱うかが問題。
modelを整える
関係性を把握
あるユーザーはいくつもレビューする
あるレビューは一人のユーザーによって作られる
あるレビューは一つの本を対象にする
関係性は次のようになる。

ひとつの結合
usersとreviewsの関係性だけみてみる。
まずusersのmodel
class User < ApplicationRecord
has_many :reviews
end
has_many
なのでreviews
と複数形。
これを設定すればUser.joins(:reviews)
が使えるようになる。
:reviews
はmodelで定義したとおりに複数形
User.joins(:reviews)
=> "SELECT `users`.* FROM `users`
INNER JOIN `reviews` ON `reviews`.`user_id` = `users`.`id`"
reviewsのmodelも設定してみる。
class Review < ApplicationRecord
belongs_to :user, optional: true
end
これを設定すればReview.joins(:user)
が使えるようになる。
belongs_to
だからuser
と単数形。
Review.joins(:user)
=> "SELECT `reviews`.* FROM `reviews`
INNER JOIN `users` ON `users`.`id` = `reviews`.`user_id`"
任意の主キー,外部キー
primary_key
とforeign_key
を使って、任意の外部キー code を設定する。
※ codeではなくbook_idとして紐付ければよいのだが、任意の外部キー設定をとりあげるためにcodeとしている。
class Review < ApplicationRecord
belongs_to :user, optional: true
belongs_to :book, primary_key: :id, foreign_key: :code, optional: true
end
class Book < ApplicationRecord
has_many :reviews
end
このように設定すると複数の結合をBook.joins(reviews: :user)
と書くことができる。
Book.joins(reviews: :user)
=> "SELECT `books`.* FROM `books`
INNER JOIN `reviews` ON `reviews`.`code` = `books`.`id`
INNER JOIN `users` ON `users`.`id` = `reviews`.`user_id`"
目的の条件で取り出す
目的としていた**「女性が評価4.0以上レビューした本のタイトル一覧」**を取り出すには、
Book
.joins(reviews: :user)
.select('books.title')
.where('users.gender = ?', 'woman')
.where('reviews.evaluation >= ?', 4.0)
=> "SELECT `books`.* FROM `books`
INNER JOIN `reviews` ON `reviews`.`code` = `books`.`id`
INNER JOIN `users` ON `users`.`id` = `reviews`.`user_id`
WHERE users.gender = 'woman'
AND reviews.evaluation >= 4.0"
以上。
これで複数にまたがる結合からの抽出が完了