Страницы

Поиск по вопросам

среда, 10 июля 2019 г.

Как составить запрос ActiveRecord

Есть модель User и зависимая модель Friend, которая, кроме всего прочего содержит u_id - user_id который этот друг имеет как пользователь - член модели User. Как получить друзей всех друзей (хочу определить есть ли я сам в этом списке - взаимность дружбы)?
Поскольку у меня есть u_id, я думал, что все будет просто: сначала я нахожу своих @profile.friends.all потом в User всех этих friends по u_id, а потом - friends этих User (ну а там уже себя нахожу или не нахожу). Но - никак не смог продвинуться дальше понимания, все варианты не работают.


Ответ

С радостью сообщаю, что в комментариях я почти ошибся.
То есть, такой запрос написать действительно непросто, но с этим можно справиться с помощью ассоциаций. К сожалению, решение не очень чистое. Не стоит слишком много хотеть от языка запросов ActiveRecord, он не предназначен для сложных случаев.
Трюк состоит из рекурсивного has_many :through. Рекурсивного, т. к. ассоциация связывает записи с записями того же типа
Я приведу изолированный пример. Внешние ключи в нём, вероятно, не будут соответствовать вашему случаю, доработайте под себя.
class Friend belongs_to :user belongs_to :u, class_name: 'User'
has_many :through_friends, class_name: self.name, primary_key: :u_id, foreign_key: :user_id end
class User has_many :friends end
Имея такие ассоциации, можно сделать следующий запрос:
puts User.joins(friends: :through_friends).to_sql
Это выведет запрос, содержащий self-join (самосоединение) таблицы friends. Причём сначала она будет без алиаса, а потом с алиасом.
Здесь используется недокументированная деталь реализации! Алиас, похоже, генерируется детерминированно. Во всяком случае, в каждой конкретной версии Rails можно ожидать, что он не поменяется, но чтобы не получить внезапных багов, используйте тесты, обновляйтесь осторожно, или даже сделайте на уровне класса (чтобы падало при инициализации) предупреждение:
unless ActiveRecord.version.to_s = '4.2.6' #или какая у вас там версия сейчас raise NotImplementedError, 'implementation detail from another version' end Или каким-нибудь более продвинутым способом, на ваш вкус.
Вам нужно взять этот алиас (у меня это оказался through_friends_friends) и наложить по нему условия примерно так:
User.joins(friends: :through_friends).where( through_friends_friends: { u_id: @user.id } )
В теории, должен получиться список "друзей друзей" @userа. Чтобы не получить дубликаты (из-за JOIN'ов), стоит добавить .distinct. А если вы проверяете только дружбу, а не достаёте пользователей, можно исключить из рассмотрения User'а и работать сразу с Friend
@user.friends.joins(:through_friends).where( through_friends_friends: { u_id: @user.id } ).exists?
Экспериментируйте.

Комментариев нет:

Отправить комментарий