1

我有一个论坛。每个用户都有帖子和回复。每个帖子都有回复。每当用户查看帖子时,我都会在 Views 表中创建一条记录(或使用最后一个 reply_id 更新它)。假设一个帖子还没有任何回复,那么我只需保存 user_id 和 post_id。如果有对帖子的回复,那么我保存 user_id、post_id 和在帖子查看时最后回复的回复 ID。

我正在尝试创建一个“搜索未读帖子”页面,向用户显示他们尚未阅读的任何帖子,或者是否对他们已经查看的帖子有新的回复。我相信你们中的大多数人都熟悉这个原则,因为大多数论坛引擎都有这种搜索,但我想确保不要留下任何问题。

以下是关联(除去所有权限检查和其他关联):

class User < ActiveRecord::Base
  has_many :posts
  has_many :views
end

class Post < ActiveRecord::Base
  belongs_to :user
  has_many :replies
  has_many :views
  has_one :latest_reply, :order => "created_at DESC", :class_name => "Reply", :conditions => { :replystatus => 'published' }
end

class Reply < ActiveRecord::Base
  belongs_to :post
  belongs_to :user
  has_many :views
end

class View < ActiveRecord::Base
  belongs_to :post
  belongs_to :reply
  belongs_to :user
end

我想知道如何只获取用户根本没有阅读的帖子(视图中没有记录),或者帖子 latest_reply.id 大于保存在视图中的帖子。我已经想出了如何在 sql 中编写这个查询(使用连接、子查询和联合),但我想知道是否有人知道用 Rails 方式来执行此操作。我猜一些智能(链式)范围会起作用,但我就是无法理解它。

4

4 回答 4

0

我为@BillyChan re 添加了一条评论:发布和查看计数。

回复:latest_reply,我认为您应该将其作为范围推入回复模型:

scope :latest, order('created_at DESC').first
scope :published, where(:replystatus => 'published')

随后,您可以通过 post.views.published.latest / reply.views.published.latest 访问它,并根据需要进行比较。

于 2013-02-21T06:13:48.940 回答
0

使用中介表是正确的方法,但它通常太重而无法处理,即使使用计数器缓存或其他东西(想象一下 View 表中的百万或记录......)。

所以我可以提供一个更轻、更快的选项,而无需任何连接表。

User has_many Posts and Replies
Posts has_many Replies

Post 有一个属性:last_viewed(datetime)。每次当用户查看帖子的回复时,属性都会更新为特定回复的created_at值(如果它更新)。

现在:

Reply.includes(:post => :user).where(["users.id = ? AND posts.last_viewed < replies.created_at", @user.id]) # all replies belongs_to post, which ones the post's author (user) has never seen.

# and

Post.includes(:replies).where(["posts.user_id = ? AND posts.last_viewed < replies.created_at", @user.id]) # all user's posts having replies, which he has never seen.

这种方法最常用于论坛引擎。因为它很快。

[更新]所有用户和所有帖子和所有回复:

向用户添加一个属性:(last_view_posts_at日期时间)当用户打开它时,该属性必须用 Post 或 Reply 的created_at值(如果更新)进行更新。

Reply.includes(:post).where(["posts.created_at > ? OR replies.created_at > ?", @user.last_view_posts_at.to_s(:db)]) # all replies which the @user has never seen.

# and

Post.includes(:replies).where(["posts.created_at > ? OR replies.created_at > ?", @user.last_view_posts_at.to_s(:db)]) # all unseen posts and post having unseen replies.

问题是您无法管理特定帖子和回复的已读/未读,因为它取决于时间戳。你必须玩“last_view_posts_at”。假设您有 3 条未读消息。之后,您打开最旧的,然后阅读最新的,在这种情况下,第二个(中间)将不再是“未读”。

于 2013-02-21T17:57:29.017 回答
0

我能够微调服务于我提出的问题的实际查询。这是实际的查询(user_id 在本例中是硬编码的,但在控制器中是@current_user.id):

select posts.id, posts.title
  from posts
 where posts.id not in (
        select p.id
          from (select posts.id, max(replies.id)
              from posts
              left outer join replies on replies.post_id = posts.id
             group by posts.id
            INTERSECT
            select views.post_id, views.reply_id
              from views
             where views.user_id = 1 ) as p )

这是我在控制器中的方式(将其作为范围移动到模型中):

@posts = Post.includes(:latest_reply).where('posts.id not in ( select p.id from (select posts.id, max(replies.id) from posts left outer join replies on replies.post_id = posts.id group by posts.id INTERSECT select views.post_id, views.reply_id from views where views.user_id = ? ) as p )', @current_user.id).page(params[:page]).per(15)

我知道它不漂亮,但是我已经尝试弄清楚如何执行这种“Rails Way”将近两天了,现在只需要这样做,因为我还有更多的编码要做:)

如果有人有任何提示或如何通过“Rails 方式”执行此操作或如何加快此查询(速度非常快),我将不胜感激您的所有意见。

于 2013-02-21T20:09:09.500 回答
0

新答案

根据 OP 的评论,我意识到我误解了前一个答案中的问题。已读/未读跟踪深入到用户级别。我没有这样的经验,但可以为头脑风暴提供两个想法。

  1. 数据库方式。你保留你现在所做的。但是对于创建的每个新帖子,您将每个用户的记录添加到 View 表中,每个用户的初始值为零视图计数。通过这种方式,您可以使用 SQL 检查特定用户的未读帖子。缺点是服务器工作量大,表很大,也许你可以使用cron添加查看记录,使用缓存来存储用户的未读帖子。

  2. 数组比较方式。您将字段添加到用户表存储用户查看的帖子 ID。对于他查看的每个帖子,该字段将使用添加的帖子 ID 进行更新。然后,当您显示正常的帖子列表(例如最近的帖子)时,您可以比较此 id 是否在此字段中(转换为数组),如果没有,则将帖子标记为未读。要显示未读帖子的完整列表,请使用 SQL 搜索不在此字段中的帖子 ID(SELECT id FROM posts WHERE id NOT IN the_id_list_arrary)

随意批评。

由于对问题的误解而放弃了以前的答案

我的建议是添加一个名为view_countPost 的字段,默认值为 0。

每次您View使用有关 Post 的新记录写入表时,您view_count也会增加 1。

要搜索未读帖子,您可以简单地搜索 view_count 为 0 的帖子。

通过这种方法,您将在查询时节省大量服务器资源,因为 View 表将非常大,并且在您的问题中查询它的方式非常繁重。

可选的未来增强功能:

  1. 您可以设计一个任务来通过 View 表重建 view_count,并手动或通过 cron 运行它。

  2. 您甚至可以在将来根据需要添加 view_count_month、view_count_year、view_count_all 字段。使用重建机制,维护这些字段将很容易。

毕竟,在我看来,基本的解决方案应该已经足够好了。

于 2013-02-21T05:32:46.087 回答