1

フォーラムがあります。すべてのユーザーには投稿と返信があります。すべての投稿には返信があります。ユーザーが投稿を表示するたびに、Views テーブルにレコードを作成します (または最後の reply_id で更新します)。投稿にまだ返信がない場合は、user_id と post_id を保存します。投稿への返信がある場合は、投稿を表示した時点での最後の返信の user_id、post_id、reply_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

質問に対応する実際のクエリを微調整することができました。実際のクエリは次のとおりです(この例では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」をほぼ2日間行う方法を見つけようとしてきました、そして私はもっとたくさんのコーディングをする必要があるので今のところやらなければなりません:)

誰かがこれを行うためのヒントや方法、「Rails Way」、またはこのクエリを高速化する方法(驚くほど高速です)があれば、すべての入力に感謝します。

于 2013-02-21T20:09:09.500 に答える
0

新しい答え

OPのコメントに基づいて、以前の回答で質問を誤解していることに気付きました。既読/未読の追跡は、ユーザーレベルまで非常に深いものです。私はそのような経験はありませんが、ブレインストーミングのために 2 つの考えを提供できます。

  1. データベースの方法。あなたは今していることを守ります。ただし、新しい投稿が作成されるたびに、各ユーザーのレコードをビュー テーブルに追加し、各ユーザーのビュー カウントの初期値を 0 にします。これにより、SQL を使用して、特定のユーザーの未読の投稿を確認できます。欠点は、サーバーのワークロードが重くなり、テーブルが巨大になることです。おそらく、cron を使用して View レコードを追加し、キャッシュを使用してユーザーの未読の投稿を保存できます。

  2. 配列比較方法。ユーザーテーブルにフィールドを追加して、ユーザーが閲覧した投稿IDを保存します。彼が閲覧した投稿ごとに、追加された投稿 ID でフィールドが更新されます。次に、投稿の通常のリスト (最近の投稿など) を表示すると、この ID がこのフィールド (配列に変換) に含まれているかどうかを比較できます。そうでない場合は、投稿を未読としてマークします。未読の投稿の完全なリストを表示するには、SQL を使用して、このフィールドにない投稿 ID を検索します (SELECT id FROM posts WHERE id NOT IN the_id_list_arrary)

気軽に批判してください。

質問の誤解により、以前の回答を放棄しました

私の提案はview_count、デフォルト値が 0 の Post という名前のフィールドを追加することです。

ViewPost に関する新しいレコードをテーブルに書き込むたびに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 に答える
0

@BillyChan re: Post and view count にコメントを追加しました。

Re: latest_reply、スコープとして 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

mediate テーブルを使用するのは正しい方法ですが、カウンター キャッシングやその他のものを使用しても、処理するには重すぎることがよくあります (ビュー テーブルの数百万またはレコードを想像してみてください...)。

したがって、接続テーブルを使用せずに、はるかに軽量で高速なオプションを提供できます。

User has_many Posts and Replies
Posts has_many Replies

投稿には属性last_viewed(日時) があります。また、ユーザーが投稿の返信を表示するたびに、属性が特定の返信の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(datetime) ユーザーが属性を開いたときに、その属性を投稿または返信の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 つの未読メッセージがあるとします。その後、最も古いものを開き、最新のものを読みます。その場合、2 つ目 (中間) は「未読」にはなりません。

于 2013-02-21T17:57:29.017 に答える