ActiveRecord AND запрос - PullRequest
       6

ActiveRecord AND запрос

0 голосов
/ 20 апреля 2020

У меня есть 3 модели: пользователь, Conversation и ConversationUser

class User < ApplicationRecord
  has_many :conversation_users, dependent: :destroy
  has_many :conversations, through: :conversation_users
end
class Conversation < ApplicationRecord
  has_many :conversation_users, dependent: :destroy
  has_many :users, through: :conversation_users
end
class ConversationUser < ApplicationRecord
  belongs_to :conversation
  belongs_to :user
end

Моя цель

Я хотел бы найти разговор, когда 2 пользователя имеют одинаковый разговор (разговор должен иметь только 2 conversation_users с идентификатором этих 2 пользователей)

Для теста

Я создал только ОДИН conversation с ДВУМЯ conversation_users

conversation = Conversation.create
conversation.conversation_users.create(user: User.first)
conversation.conversation_users.create(user: User.second)

Моя проблема

Я хочу найти разговор с ПРОСТО первым пользователем И вторым пользователем , Если я сделаю следующий запрос, Active Record все равно покажет мне разговор, потому что он делает предложение OR, но мне нужен AND. Правильный результат должен показать мне пустой массив, потому что нет разговора с User.first И User.third

Conversation.joins(:conversation_users).where(conversation_users: {user_id: [User.first.id, User.third.id]})

  User Load (3.3ms)  SELECT "users".* FROM "users" ORDER BY "users"."created_at" ASC LIMIT $1  [["LIMIT", 1]]
  User Load (2.5ms)  SELECT "users".* FROM "users" ORDER BY "users"."created_at" ASC LIMIT $1 OFFSET $2  [["LIMIT", 1], ["OFFSET", 2]]
  Conversation Load (3.9ms)  SELECT "conversations".* FROM "conversations" INNER JOIN "conversation_users" ON "conversation_users"."conversation_id" = "conversations"."id" WHERE "conversation_users"."user_id" IN ($1, $2) LIMIT $3  [["user_id", "274d2f54-2418-4dec-a696-a5f62196ee85"], ["user_id", "0ef2f797-3518-4096-951b-1837ca28e4e4"], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Conversation id: "35556705-a162-4ee4-9776-963ae87bcfa8", created_at: "2020-04-20 09:34:05", updated_at: "2020-04-20 09:34:05">]>

Ожидания

  • Должен найти a Разговор с User.first и User.second
  • НЕ должен находить Разговор с User.first и User.third

Я пробовал этот запрос для первого ожидания, но он возвращается пустой массив

Conversation.joins(:conversation_users).where(conversation_users: {user_id: User.first.id}).where(conversation_users: {user_id: User.second.id})

  User Load (3.8ms)  SELECT "users".* FROM "users" ORDER BY "users"."created_at" ASC LIMIT $1  [["LIMIT", 1]]
  User Load (4.0ms)  SELECT "users".* FROM "users" ORDER BY "users"."created_at" ASC LIMIT $1 OFFSET $2  [["LIMIT", 1], ["OFFSET", 1]]
  Conversation Load (3.3ms)  SELECT "conversations".* FROM "conversations" INNER JOIN "conversation_users" ON "conversation_users"."conversation_id" = "conversations"."id" WHERE "conversation_users"."user_id" = $1 AND "conversation_users"."user_id" = $2 LIMIT $3  [["user_id", "274d2f54-2418-4dec-a696-a5f62196ee85"], ["user_id", "44a0c35a-b5c1-4bb6-a8a3-30ae3d4b3345"], ["LIMIT", 11]]
=> #<ActiveRecord::Relation []>

Ответы [ 2 ]

1 голос
/ 21 апреля 2020
class Conversation < ApplicationRecord
  has_many :user_conversations
  has_many :users, through: :user_conversations

  def self.between(*users)
    users.map do |u|
      where("EXISTS(SELECT * FROM user_conversations uc WHERE uc.conversation_id = conversations.id AND uc.user_id = ?)", u)
    end.reduce(&:merge)
      .joins(:user_conversations)
      .group(:id)
      .having('count(*) = ?', users.length)
  end
end

Не самое чистое решение, но оно проверяет наличие записи о присоединении для каждого пользователя, а затем .having('count(*) = ?', users.length) гарантирует, что записей о присоединении больше, чем пользователей.

Пример использования:

Conversation.between(User.first, User.second)
Conversation.between(1,2,3)
Conversation.between(*User.all) # splat it like a pro

Это приводит к следующему SQL запросу:

SELECT "conversations".* FROM "conversations" 
INNER JOIN "user_conversations" ON "user_conversations"."conversation_id" = "conversations"."id" 
WHERE 
  (EXISTS(SELECT * FROM user_conversations uc WHERE uc.conversation_id = conversations.id AND uc.user_id = 1))
  AND 
  (EXISTS(SELECT * FROM user_conversations uc WHERE uc.conversation_id = conversations.id AND uc.user_id = 2)) 
GROUP BY "conversations"."id"
HAVING (count(*) = 2) 
LIMIT $1
0 голосов
/ 20 апреля 2020

Вы можете сделать запрос с условием AND следующим образом

Conversation.joins(:conversation_users).where(conversation_users: {user_id: User.first.id}).where(conversation_users: {user_id: User.second.id})
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...