1

我正在为 Rails 应用程序编写一个 facebook 风格的消息系统,但在选择收件箱的消息时遇到了问题(使用 will_paginate)。

消息以线程的形式组织,在收件箱中,线程的最新消息将显示,并带有指向该线程的链接。线程是通过与自身的 parent_id 1-n 关系组织的。

到目前为止,我正在使用这样的东西:

class Message < ActiveRecord::Base
  belongs_to :sender, :class_name => 'User', :foreign_key => "sender_id"
  belongs_to :recipient, :class_name => 'User', :foreign_key => "recipient_id"
  has_many :children, :class_name => "Message", :foreign_key => "parent_id"
  belongs_to :thread, :class_name => "Message", :foreign_key => "parent_id"
end

class MessagesController < ApplicationController

  def inbox
    @messages = current_user.received_messages.paginate :page => params[:page], :per_page => 10, :order => "created_at DESC"
  end
end

这给了我所有的消息,但是对于一个线程,线程本身和最新消息将出现(而不仅仅是最新消息)。我也不能使用 GROUP BY 子句,因为对于线程本身(可以说是父级),parent_id = nil 当然。

任何人都知道如何以优雅的方式解决这个问题?我已经考虑过将 parent_id 添加到父级本身,然后按 parent_id 分组,但我不确定这是否有效。

谢谢

4

5 回答 5

0

我的解决方案是获取线程列表(我假设可以通过没有父 ID 的消息获得)。然后在 Message 模型上,添加一个方法,该方法将在线程中查找最新消息并返回它。然后,您可以使用该方法获取每个线程中的最新方法,并轻松放入指向线程头部的链接。

(伪)代码:

class Message < ActiveRecord::Base
  belongs_to :sender, :class_name => 'User', :foreign_key => "sender_id"
  belongs_to :recipient, :class_name => 'User', :foreign_key => "recipient_id"
  has_many :children, :class_name => "Message", :foreign_key => "parent_id"
  belongs_to :thread, :class_name => "Message", :foreign_key => "parent_id"

  def get_last_message_in_thread()
    last_message = self
    children.each do |c|
       message = c.get_last_message_in_thread()
       last_message = message if message.created_at > last_message.created_at
    end
    return last_message
  end
end

class MessagesController < ApplicationController

  def inbox
    @messages = current_user.received_messages.find_by_parent_id(Null).paginate :page => params[:page], :per_page => 10, :order => "created_at DESC"
  end
end

与使用递归函数查找线程中的最后一条消息相比,您可能做得更好,但这是我能想到的最简单的解决方案来演示这个想法。我也不确定在收件箱函数中查找未设置的父 ID 的语法是否正确,这就是我将代码标记为伪代码的原因:)

于 2009-05-31T19:44:39.167 回答
0

唯一有效的方法是拥有一个 Thread 模型并使用 GROUP BY 正如您所提到的 - 其他任何事情都需要对消息进行迭代。

阅读评论中的更新

于 2009-05-31T20:11:56.477 回答
0

将父级本身作为父级可以很容易地创建在整个线程上运行的查询,因为您可以按 parent_id 分组(或任何类似的)。

如果您以不同的方式处理父母,那么您的所有查询也必须考虑到这一点

于 2009-05-31T20:19:20.347 回答
0

我不知道如何在 Rails 中做到这一点,但这是我直接在 MySQL 中做到的:

select * from messages where message_id in ( select max(message_id) from messages where to_uid = 51 group by thread_id ) order by timestamp desc

我使用子查询来获取线程中的最新消息,然后使用主查询来获取子查询中找到的消息的所有字段。

于 2010-05-20T01:09:23.990 回答
0

我认为唯一好的解决方案是使用第二个模型来存储每个线程的最新消息(因为将 GROUP BY 与子选择一起使用时的性能问题,请参阅我的评论)。它不会在数据库中占用太多空间,因为我们只存储 id 而没有文本甚至 blob。

最近消息模型看起来像这样:

create_table :recent_messages do |t|
  t.integer :sender_id
  t.integer :recipient_id
  t.integer :message_id
  t.integer :message_thread_id

  t.timestamps
end

class RecentMessage < ActiveRecord::Base

  belongs_to :message
  belongs_to :message_thread, :class_name => 'Message'
  belongs_to :sender, :class_name => 'User', :foreign_key => "sender_id"
  belongs_to :recipient, :class_name => 'User', :foreign_key => "recipient_id"

end

主要思想是:所有的消息都存储在一个模型(Messages)中。每当向线程添加新消息(或创建线程)时,都会发生两件事(例如,使用 after_save 回调):

  • 将新消息存储在 RecentMessages 模型中(即 sender_id、receiver_id、message_id、message_thread_id (= parent_id || id))
  • 获取最新消息(来自消息中的这个线程),其中 sender_id == recipient_id 反之亦然(注意:这仅适用于消息模型应仅支持 2 个用户之间的消息)并将其存储在 RecentMessages 模型中(如果找到,如果它还没有)

当然应该只有最大值。在任何给定时间,为每个 message_thread 存储在数据库中的 2 个最近消息。

如果要显示收件箱,则必须执行以下操作:

@messages = current_user.recent_received_messages.paginate :page => params[:page], :per_page => 10, :order => "created_at DESC", :include => :message

这是迄今为止我想出的最好的。我仍然认为它很丑,但它很快而且有效。如果有人提出更好的解决方案,我将不胜感激!

于 2009-06-03T08:57:42.930 回答