按带分页的关联模型排序

Order by associated model with pagination

rails: '4.2.10' 雷:“1.0.1”

我正在处理一个非常简单的关系,其中 Conversation 有许多 ConversationMessages。我在索引上显示这些 Conversation 记录,并希望按最近 ConversationMessage 的记录对它们进行排序。这就是事情变得有趣的地方。

def index
    @conversations = current_user.conversations.includes(:conversation_messages).order("conversation_messages.created_at DESC").page(params[:page])
end

发生的事情是当我使用 conversation_messages.created_at 订购时,大多数记录没有显示在视图中,事实上分页完全消失了;然而,如果我将 ?page=3 附加到 url,您仍然可以访问其他页面;他们每个人的记录数量都非常有限。

此外,如果我删除 .page(params[:page]) 并摆脱分页,所有结果都会显示我是按 Conversation 日期还是 ConversationMessage 日期订购。

只是当按 ConversationMessage 排序并且同时使用分页时,一切似乎都变得一团糟。

有什么想法吗?

基本上您需要明智地对结果对话进行分组,因为从对话到 conversation_messages 存在一对多关系。我会做这样的事情

@conversations = current_user.conversations
  .joins(:conversation_messages)
  .group("conversations.id, recent_message_date")
  .order("recent_message_date DESC")
  .select("conversations.*, conversation_messages.created_at as recent_message_date")
  .page(params[:page])

希望对您有所帮助。

查询适合 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 但也会导致错误。

要做到这一点,您需要使用子查询。您可以通过使用 ActiveRecord 鲜为人知的 #from 方法在 Rails 环境中执行此操作。这允许您生成一个子查询,然后对该子查询进行操作,例如,通过对其进行排序或使用 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 子句时才存在。

为了一成不变,我们必须给子查询一个名字(你会想使用 table 的名字以避免出现问题),然后从子查询中获取所有内容。然后我们可以计算结果记录,我们也可以可靠地根据该属性进行排序或过滤:

>> @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

或者,如果您愿意,可以将方法编写为 class 的范围;结果是一样的。现在您可以编写清晰、富有表现力的查询:

>> 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 尝试了类似的查询,它按预期工作。