トップ画像イメージ

ActiveRecordの内外結合って便利ですよね

こんにちは。 以前ActiveRecordの内部結合について、記事を書かせていただきましたが、

ActiveRecordのモデル同士の内外結合とっても便利ですよね。

例えば、 usersというテーブルの1レコードに対して、複数のcontentsというテーブルのレコードが紐づいているとします。

リレーション的に言えば、

users 1 対 多 contents

users 1 - * contents

というような感じです。 usersテーブルのidとcontentsテーブルのuser_idというカラムでテーブルを結合する場合(結合はinner join)、生sqlとActiveRecordとの書き方をそれぞれ記載すると、次のような形になります。

sql


SELECT users.*,contents.*
FROM users u
  INNER JOIN contents c
  ON  u.id = c.user_id

ActiveRecord

#users.rb(model)
has_many :contents

#contents.rb
belongs_to :users

##↑のようにモデルに定義した上で、下のようなロジックで

User.includes(:contents)

という風に書くことができます。

ここから、返ってくる結果をeach文などで処理して行きたいところですが、生SQLとActiveRecordでは当然返す値が異なりますし、each文のアプローチも異なってきます。

SQLとの違い

上のような処理を実行した場合、sqlでは次のような結果が返ってきます。

users.id users.name users.kana_name contents.id contents.user_id
1 飯塚 イイヅカ 1 1
2 豊本 トヨモト 1 2
3 角田 カクタ 1 3

イメージ的に言えば、結合したテーブルのレコードが返ってくるから、このレコードを1行、1行eachで回しながら処理をするというイメージになるかと思います。

一方で、ActiveRecordの場合、次のような形になります。


results = User.includes(:contents)
#次のようなSQLが実行される。
>SELECT "users.*".* FROM "users"
>SELECT "contents".* FROM "contents"  WHERE "userss"."id" IN (`1,2,3`)


#返り値として、ActiveRecord_Relationクラスのインスタンスが返される
#eachで回そうとすると、このような形になる
results.each do |user|  
 #このuserという変数にusersテーブルの1レコードの情報が含まれている。
 #情報を取り出すには、user.idのように呼び出す
  user.contents.each do |content|
    #このcontentという変数にcontentテーブルの1レコードの情報が含まれている。
    ~行いたい処理~
  end
end

ActiveRecordのincludeメソッドやjoinsメソッドを実行した場合に、 結合したテーブルのレコードを横一列の配列にして返すのではなく、ActiveRecordRelationクラスのインスタンスが返って来ます。

「結合したテーブルの各列をsqlのjoinのように、横一列にして値を返すのではなく、あらかじめモデルに関連付けていたとおり、usersの1レコードに紐づいた、1以上のcontentsテーブルのレコード達」という形で値を返すというイメージかな?

例えば、このレコード達の中から、 usersテーブルのnameおよびcontentsテーブルのmeigenを取り出したい場合、

users.id users.name users.kana_name contents.id contents.user_id contents.meigen
1 飯塚 イイヅカ 1 1 蓄積に操られたモンスターなんだよー!
2 豊本 トヨモト 1 2 お尻舐めたげる
3 角田 カクタ 1 3 若者の逆ギレが怖ぇ〜!

Railsの場合

results = User.includes(:contents)

results.each do |user|
  user.contents.each do |content|
      puts user.name
      puts content.meigen
  end
end

このように、each文でusersに紐付くcontentsの値を出力してあげる形になります。

TIP

results[0].user_name => "飯塚"results[0].contents[0].id => 1という風にレコードの値を取り出すことが出来ます。

まとめ

ActiveRecordのincludesメソッドの良いところは、

  • 明示的に取り出す値をselectしないで良い。
    • 結合するテーブルに重複する列名があれば、別名を付けて重複を避ける・・・みたいなロジックの記載が不要
  • 自由にレコードの値を取り出せるため、テーブル定義変更や、列追加などにも柔軟に対応できる。
  • 実行されるSQLの結果をキャッシュしてくれるので、results[0].user_nameのようなメソッドを何度呼び出しても、無駄なSQLの発行を防止してくれる。
  • 記述するコード量も少ない
  • Rails使いの皆様には常識かもしれませんが、最初は横にusersとcontentsが繋がった結果がresultsの一要素として返ってくるものと思っていた😢
テーブルの追加や、テーブル定義の仕様変更においても、適切にアソシエーションを貼り直せば良いだけなので、積極的に使っていきたいですね。