joinsメソッドについて

前提条件

モデル

User has many pets のアソシエーションを行っています。

class User < ApplicationRecord
  has_many :pets     # <= 関連名
en

class Pet < ApplicationRecord
  belongs_to :user   # <= 関連名
end

データ

usersテーブル

Image from Gyazo

petsテーブル

Image from Gyazo

joinsメソッドを使用してみる

User.joins(:pets)

実行すると以下のSQLが発行されます

SELECT "users".* FROM "users" INNER JOIN "pets" ON "pets"."user_id" = "users"."id" 

joinsはデフォルトで内部結合(INNER JOIN)を行います。
又、SQLからusersテーブルからpetsテーブルのuser_idカラムとusersテーブルのidカラムがマッチしているレコードのみを取得しているのが分かります。

結果がこちらです。

Image from Gyazo

結論

基本形

モデル名.joins(:関連名)

取得するのはモデルのレコードのみ。=> 結合先のレコードを取得する為のメソッドではない!!

重複データを削除する(distinct)

先程の結果を見ると、たなかおぎというレコードが重複して発生しています。 この際、重複してレコードを削除してくれるのがdistinctメソッドです。

User.joins(:pets).distinct

実行されるSQL

SELECT DISTINCT "users".* FROM "users" INNER JOIN "pets" ON "pets"."user_id" = "users"."id"

SELECTの後にDISTINCTが追加されているのが分かります。

結果がこちらです。

Image from Gyazo

先程と違い、重複したレコードがなくなっているのが分かります。

selectメソッドを使用して取得するカラムを変更する。

参照先(ここではpets)のカラムを取得したい場合はselectを使用します。

例えばpetsテーブルのspeciesも取得したい場合は

User.joins(:pets).select('users.*, pets.species')

発行されるSQL

SELECT users.*, pets.species FROM "users" INNER JOIN "pets" ON "pets"."user_id" = "users"."id"

結果はこちら
ちゃんと追加されているのが分かります。

Image from Gyazo

結論

selectを使用すれば、参照先のレコードも取得できる!!
しかし、キャッシュしないので結合先のレコードを取得するのには向いていない

whereを使用して条件を設定する

まずは例として以下を実行してみます。

User.joins(:pets).where(id:1)

発行されるSQL

SELECT "users".* FROM "users" INNER JOIN "pets" ON "pets"."user_id" = "users"."id" WHERE "users"."id" = 1

where(id:1)は、SQLではWHERE "users"."id" = 1を指しています。
このことから、whereメソッドはusersテーブルの条件を指していることが分かります。

結果はこちらです。

Image from Gyazo

petsのテーブルの条件を指定するには以下のように行います。

 User.joins(:pets).where(pets: {id:1})

発行されるSQL

SELECT "users".* FROM "users" INNER JOIN "pets" ON "pets"."user_id" = "users"."id" WHERE "pets"."id" = 1

where(pets: {id:1})が、SQLではWHERE "pets"."id" = 1を指しているのが分かります。

結果はこちらです。

Image from Gyazo

結論

  • 結合元の条件を設定する場合
モデル名.joins(:関連名).where(カラム名: 値)
  • 結合先の条件を設定する場合
モデル名.joins(:関連名).where(テーブル名: { カラム名: 値 } )