ActiveRecordには同じjoinsを何度チェーンしても1回しかJOINしないという便利機能が備わっています。
例えば下記のように同じjoinsを3回書いても、実際に発行されるSQLでは1回しかJOINされていません。
この機能は複雑なSQLを作る時にすでにJOINされているかを意識せずに実装できるのでとても便利です。
irb(main):016:0> User.joins(:user_books).joins(:user_books).joins(:user_books)
User Load (9.6ms) SELECT `users`.* FROM `users` INNER JOIN `user_books` ON `user_books`.`user_id` = `users`.`id`
次に多対多のモデルをJOINする場合を考えます。
下記のモデルではUserとBookは多対多の関係です。UserモデルにBookモデルをJOINする実装を考えます。
class User < ApplicationRecord
has_many :user_books
end
class UserBook < ApplicationRecord
belongs_to :user
belongs_to :book
end
class Book < ApplicationRecord
has_many :user_books
end
単純に実装すると下記の実装が考えられます。
実行するとusersからuser_booksを経由してbooksをJOINしたクエリーが発行されます。
irb(main):003:0> User.joins(user_books: :book)
User Load (8.8ms) SELECT `users`.* FROM `users` INNER JOIN `user_books` ON `user_books`.`user_id` = `users`.`id` INNER JOIN `books` ON `books`.`id` = `user_books`.`book_id`
ActiveRecordにはhas_many-throughという便利機能あります。
throughを使う場合、モデルに下記を追記します。
class User < ApplicationRecord
has_many :user_books
+ has_many :books, through: :user_books
end
throughを使うと下記のように実装することができます。中間テーブルを意識しなくて良くなるので少し楽です。
throughを使わない場合と同じSQLが発行されているので問題なさそうです。
irb(main):003:0> User.joins(:books)
User Load (9.9ms) SELECT `users`.* FROM `users` INNER JOIN `user_books` ON `user_books`.`user_id` = `users`.`id` INNER JOIN `books` ON `books`.`id` = `user_books`.`book_id`
では、全く同じSQLが発行されるjoins(user_books: :book)
とjoins(:books)
をチェーンした場合は同じJOINと判断されるのでしょうか?
タイトルに書いてあるのでお察しですが、この場合は同じものと判断されません。
下記のように別名をつけて2回JOINされてしまいます。
irb(main):017:0> User.joins(:books).joins(user_books: :book)
User Load (2.3ms) SELECT `users`.* FROM `users` INNER JOIN `user_books` ON `user_books`.`user_id` = `users`.`id` INNER JOIN `books` ON `books`.`id` = `user_books`.`book_id` INNER JOIN `user_books` `user_books_users` ON `user_books_users`.`user_id` = `users`.`id` INNER JOIN `books` `books_user_books` ON `books_user_books`.`id` = `user_books_users`.`book_id`
has_many-throughはとても便利なのですが、上記のようにthroughを使ったjoinsと使わないjoinsが混在してしまうと冗長なクエリーを発行しています。
大抵の場合、冗長にJOINされても正しく動くことが多く気づきにくいのですが冗長なクエリーはパフォーマンスに影響を与える可能性もあるので注意しましょう!