Есть модель 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?
Экспериментируйте.
Комментариев нет:
Отправить комментарий