Ruby HATBM - Негативная фильтрация ведет себя странно - PullRequest
2 голосов
/ 06 мая 2019

Позволяет отображать схему Ruby с двумя ресурсами (пользователь и книги), которые имеют ассоциацию HABTM.

Когда я выполняю фильтрацию на основе этой ассоциации, независимо от того, есть у пользователя книга или нет, Ruby ведет себянемного странно:

2.5.3 :007 > Contact.includes(:books).count
(4.7ms)  SELECT COUNT(*) FROM "contacts"
=> 36880 

Это нормально, потому что возвращает общее значение контактов с любой ассоциацией книг.

2.5.3 :010 > Contact.includes(:books).where(books: {"id":[1,2] }).count
(34.0ms)  SELECT COUNT(DISTINCT "contacts"."id") FROM "contacts" LEFT OUTER JOIN "contacts_books" ON "contacts_books"."contact_id" = "contacts"."id" LEFT OUTER JOIN "books" ON "books"."id" = "contacts_books"."book_id" WHERE "books"."id" IN (?, ?)  [["id", 1], ["id", 2]]
=> 24864 

Что тоже хорошо, так как возвращает все контакты, которыеиметь книгу с идентификатором 1 или 2.

2.5.3 :011 > Contact.includes(:books).where.not(books: {"id":[1,2] }).count
(13.0ms)  SELECT COUNT(DISTINCT "contacts"."id") FROM "contacts" LEFT OUTER JOIN "contacts_books" ON "contacts_books"."contact_id" = "contacts"."id" LEFT OUTER JOIN "books" ON "books"."id" = "contacts_books"."book_id" WHERE "books"."id" NOT IN (?, ?)  [["id", 1], ["id", 2]]
=> 0 

Теперь эта часть не имеет смысла, потому что она должна быть полной - #associations, поэтому 36880 - 24864, оставляя мне 12016 записей, а не 0.

Или что мне не хватает?

Rails 5.2.2 ruby ​​2.5.3p105 (ревизия 2018-10-18 65156) [x86_64-darwin17]

1 Ответ

0 голосов
/ 06 мая 2019

условие where требует, чтобы в загруженных контактах была хотя бы одна книга, например он не может проверить, находится ли идентификатор книги в массиве, если нет идентификатора книги.


как я отлаживал это:

У меня есть небольшое репо, когда я пытался воспроизвести вашу проблему:

Contact.includes(:books).count # 10 (SELECT COUNT(*) FROM "contacts")
Contact.includes(:books).where(books: {"id":[1,2] }).count # 5 (LEFT OUTER JOIN x2)
Contact.includes(:books).where.not(books: {"id":[1,2] }).count # 4 (LEFT OUTER JOIN x2)

# so same as you

# we can find who those missing contacts are:
ids = Contact.includes(:books).to_a.map(&:id) -
  Contact.includes(:books).where(books: { id: [1, 2] }).to_a.map(&:id) -
  Contact.includes(:books).where.not(books: { id: [1, 2] }).to_a.map(&:id)
# I'm getting 3 ids in my case
Contact.where(id: ids).map { |c| c.books.size }
# they all have 0 books
Contact.eager_load(:books).count # 10 (with LEFT OUTER JOIN x2)
# every contact with at least one book
Contact.includes(:books).where.not(books: { id: nil }).count

# some wrong queries
Contact.includes(:books).where(books: {"id":[1,2] }).or(
  Contact.includes(:books).where(books: { id: nil })).size
# returns 8
Contact.includes(:books).where.not(books: {"id":[1,2] }).or(
  Contact.includes(:books).where(books: { id: nil })).size
# returns 7

минимальное репо репродукции: https://github.com/localhostdotdev/bug/tree/has-and-belongs-to-many-includes-count

...