通过分页的关联模型排序

时间:2018-12-06 17:37:25

标签: ruby-on-rails activerecord kaminari

rails:“ 4.2.10” kaminari:“ 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,您仍然可以访问其他页面;他们每个人都有非常有限的记录。

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

只有在按ConversationMessage进行订购并同时使用分页时,一切似乎才变得混乱。

有什么想法吗?

2 个答案:

答案 0 :(得分:1)

该查询适合于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子句时才存在。

要想一筹莫展,我们必须给子查询一个名称(您将要使用表的名称以避免出现问题),然后从子查询中获取所有内容。然后,我们可以计算结果记录,还可以可靠地对该属性进行排序或过滤:

>> @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进行类似的查询,并且它按预期工作。

答案 1 :(得分:0)

基本上,您需要明智地将结果分组进行对话,因为从对话到session_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])

希望这会有所帮助。