ActiveRecordでjoinして絞り込むときにwhereに渡す引数のキーにはテーブル名を使うべし
code:mermaid
erDiagram
User ||--o{ Book : has
User {
string name
}
code:user.rb
class User < ApplicationRecord
has_many :books
end
code:book.rb
class Book < ApplicationRecord
belongs_to :user
end
ここで、UserのnameでBookを絞り込みたいとき
code:推奨.rb
Book.joins(:user).where(users: {name: 'yuta25'})
とやる。これは以下のように書いても問題なく動く。
code:非推奨.ruby
Book.joins(:user).where(user: {name: 'yuta25'})
where に渡す引数の users: と user: の違い。発行されるSQLがちょっとちがくて、前者は
code:推奨.sql
SELECT * FROM "books" INNER JOIN "users" ON "users"."id" = "books"."user_id" WHERE "users"."name" = "yuta25"
的なSQLになるが(細部は違うかも)後者は
code:非推奨.sql
SELECT * FROM "books" INNER JOIN "users" "user" ON "user"."id" = "books"."user_id" WHERE "user"."name" = "yuta25"
的なSQLになる。後者はJOINするときにusersテーブルにuserという別名をつけてSQLを組み立てる。
単体では良いんだけど、mergeと組み合わせるとよくないときがある
code:駄目なパターン.rb
Book.joins(:user).where(user: {name: 'yuta25'}).merge(Book.where(id: 1,2,3)) code:駄目なパターン.sql
SELECT * FROM "books" INNER JOIN "users" "user" ON "user"."id" = "books"."user_id" WHERE "users"."id" IN (1,2,3) AND "user"."name" = "yuta25"
JOINのときに"users" "user"でやってるのだけど、mergeした箇所は "users"."id" IN (1,2,3)になっているので、usersなんていうテーブルはないですよ?というエラーが出る。
code:良いパターン.rb
Book.joins(:user).where(users: {name: 'yuta25'}).merge(Book.where(id: 1,2,3)) code:良いパターン.sql
SELECT * FROM "books" INNER JOIN "users" ON "users"."id" = "books"."user_id" WHERE "users"."id" IN (1,2,3) AND "users"."name" = "yuta25"
キーにテーブル名(users:)を入れる問題ないSQLになる。
Railsの公式ドキュメントには、「whereの引数、キーにはテーブル名を使う」とあり、「アソシエーション名でもいいよ」とは書いていないので、アソシエーション名でも基本的に問題がないのはたまたまよしなにやってくれているからである。
基本的にはアソシエーション名でも問題ないんだけど、前述の通りmergeと組み合わさるとダメになることがあるので、普段からテーブル名に統一しておくと良いと思う。
code:book_alternative.rb
class Book < ApplicationRecord
belongs_to :owner, class_name: 'User'
end
こういうやつだと、joinsはアソシエーション名を渡さなきゃいけないが、
code:しょうがない.rb
Book.joins(:owner).where(id: 1)
しただけで
code:しょうがない.sql
SELECT * FROM "books" INNER JOIN "users" "owner" ON "owner"."id" = "books"."owner_id" WHERE "id" = 1
みたいに、joinするときに名前がついてしまうのでしょうがない。mergeとか使わないように気をつける。