Запрос поддается joins
, а не includes
.Вы можете сделать что-то вроде одного запроса:
@conversations = current_user.conversations
.select('conversations.*, max(conversation_messages.created_at) as recent_message_date')
.left_joins(:conversation_messages).group('conversations.id').order('recent_message_date desc')
Это даст желаемый результат, но он ненадежен.Например, если вы сделаете:
>> @conversations.count
Вы получите ошибку, потому что ActiveRecord заменит предложение select
чем-то, что вы не намеревались.Вызов size
производит другой SQL, но также приводит к ошибке.
Чтобы сделать это правильно, вам нужно использовать подзапрос.Вы можете сделать это, оставаясь в среде Rails, используя малоизвестный метод ActiveRecord #from
.Это позволяет генерировать подзапрос и затем работать с этим подзапросом, например, упорядочивая его или фильтруя с помощью предложения where.
>> subquery = current_user.conversations
.select('conversations.*, max(conversation_messages.created_at) as recent_message_date')
.left_joins(:conversation_messages)
.group('conversations.id')
subquery
теперь является ассоциацией ActiveRecord, которая содержит conversation
объекты, которые вы хотите, каждый из которых имеет предварительный атрибут recent_message_date
.Я говорю предварительный, потому что (как показано ранее) атрибут существует, только если ActiveRecord не решит связываться с нашим предложением select
.
Чтобы установить вещи в камне, мы должны дать подзапросу имя (вы хотите использовать имя таблицы, чтобы избежать проблем), а затем извлечь все из подзапроса.Затем мы можем подсчитать итоговые записи и также можем надежно отсортировать или отфильтровать этот атрибут:
>> @conversations = Conversation.from(subquery, :conversations)
.order('recent_message_date desc')
Чтобы упростить задачу, мы можем создать один или два метода:
class Conversation < ApplicationRecord
def self.with_recent_message_date
select('conversations.*, max(conversation_messages.created_at) as recent_message_date')
.left_joins(:conversation_messages)
.group('conversations.id')
end
def self.most_recent_first
order('recent_message_date desc nulls last')
end
end
Или, если хотите, вы можете написать методы в качестве области видимости для вашего класса;результат тот же.Теперь вы можете писать понятные, выразительные запросы:
>> subquery = current_user.conversations.with_recent_message_date
>> @conversations = Conversation.from(subquery, :conversations).most_recent_first
Кроме того, вы можете добавить фильтр или что-то еще, используя новый атрибут:
>> @conversations.where('recent_message_date > ?', Time.current - 3.days)
, и вы должны быть в состоянии надежноразбить этот результатЯ пробовал подобный запрос, используя will_paginate
, и он работает как ожидалось.