LoginSignup
2
0

More than 1 year has passed since last update.

[ActiveRecord]throughアソシエーションのjoinsとthroughを使わないアソシエーションのjoinsを同時にチェーンした場合、同じものと判定してくれないので注意

Posted at

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されても正しく動くことが多く気づきにくいのですが冗長なクエリーはパフォーマンスに影響を与える可能性もあるので注意しましょう!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0