首页 > 解决方案 > 按带有分页的关联模型排序

问题描述

导轨:'4.2.10' kaminari:'1.0.1'

我正在处理一个非常简单的关系,其中 aConversation有很多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同时使用分页和分页排序时,一切似乎都变得混乱了。

有什么想法吗?

标签: ruby-on-railsactiverecordkaminari

解决方案


该查询适用于 ajoins而不是 a 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,它按预期工作。


推荐阅读