类似Facebook的通知功能(数据库实现)

4

我想知道Facebook是如何实现他们的通知系统的,因为我想做类似的东西。

  • FooBar在您的状态下发表了评论
  • Red1、Green2和Blue3在您的照片上发表了评论
  • MegaMan和其他5人在您的活动上发表了评论

我不能将多个通知写入单个记录中,因为最终我将为每个通知关联行动。此外,在视图中,当单个主题存在一定数量的通知时,我希望将通知呈现为可展开列表。

  • FooBar在您的状态下发表了评论(操作)
  • Red1、Green2和Pink5在您的照片上发表了评论[+]
  • MegaMan和其他3人在您的活动上发表了评论[-]
    • MegaMan在您的活动上发表了评论(操作)
    • ProtoMan在您的活动上发表了评论(操作)
    • Bass在您的活动上发表了评论(操作)
    • DrWilly在您的活动上发表了评论(操作)

干杯!

PS顺便说一句,我正在使用postgres和rails。

3个回答

4

有许多方法可以实现这一点。这取决于你想覆盖哪些通知以及你需要收集什么信息才能将其显示给正确的用户。如果你只想要一个简单的设计来覆盖有关发布评论的通知,那么可以使用多态关联观察器回调的组合:

class Photo < ActiveRecord::Base
# or Status or Event
    has_many :comments, :as => :commentable
end

class Comment < ActiveRecord::Base
    belongs_to :commenter
    belongs_to :commentable, :polymorphic => true # the photo, status or event
end

class CommentNotification < ActiveRecord::Base
    belongs_to :comment
    belongs_to :target_user
end

class CommentObserver < ActiveRecord::Observer
    observe :comment

    def after_create(comment)
        ...
        CommentNotification.create!(comment_id: comment.id,
          target_user_id: comment.commentable.owner.id)
        ...
    end
end

这里发生的情况是每张照片、状态、事件等都有很多评论。一个评论显然属于一个评论者,但同时也属于一个可评论对象。可评论对象可以是照片、状态、事件或其他你想允许评论的模型。接下来你需要一个评论观察器(CommentObserver)来监视你的评论模型,并在评论表中发生任何事情时执行一些操作。在这种情况下,当创建评论后,观察器将使用评论 ID 和拥有该注释所述内容的用户的 ID(comment.commentable.owner.id)创建一个评论通知(CommentNotification)。这需要你为每个你想拥有评论的模型实现一个简单的方法:owner。例如,如果可评论对象是一张照片,则所有者将是发布照片的用户。
这个基本设计应该足以让你开始工作了,但请注意,如果您想为除评论以外的其他事物创建通知,您可以通过在更常见的通知模型中使用多态关联来扩展此设计。
class Notification < ActiveRecord::Base
    belongs_to :notifiable, :polymorphic => true
    belongs_to :target_user
end

使用这种设计,您将“观察”所有您想要创建通知的可通知模型(notifiables),并在您的after_create回调中执行以下操作:
class GenericObserver < ActiveRecord::Observer
    observe :comment, :like, :wall_post

    def after_create(notifiable)
        ...
        Notification.create!(notifiable_id: notifiable.id, 
                           notifiable_type: notifiable.class.name,
                            target_user_id: notifiable.user_to_notify.id)
        ...
    end
end

这里唯一棘手的部分是 user_to_notify 方法。所有被标记为可通知的模型都需要根据其不同的情况实现该方法。例如,wall_post.user_to_notify 将只是墙的所有者,或者 like.user_to_notify 将是所“喜欢”的事物的所有者。甚至可能有多个人需要通知,比如当有人评论照片时,需要通知所有被标记的人。
希望这可以帮助你。

3
这会帮我避免很多麻烦,所以我会添加一笔悬赏作为感谢的表达,但在 24 小时之后才能分配。 - methodofaction
@Duopixel 非常感谢!如果您需要任何进一步的解释,请告诉我。 - cdesrosiers
谢谢。我也已经想到了这个,但没有观察者部分。现在我只是把它放在一个after_create中。但是我该如何显示像我给出的示例那样的内容呢?比如,发布评论的用户的前几个名称? - index
首先,您想在通知旁边显示哪些“操作”?您是指像“删除”,“回复”等吗? - cdesrosiers
是的。删除并回复(但会进入个人消息,所以不需要将评论与通知联系起来),还有其他一些。 - index
@Duopixel。我发布了一个新答案,它回答了我的显示问题,但我不知道它如何进行缩放和比较与 FB 的通知如何。我非常感谢反馈。 - index

1
我决定另外发一篇回答,因为第一篇已经变得非常冗长。
要像你在示例中提到的那样将评论通知呈现为可展开列表,首先需要收集针对某个目标用户具有通知的评论(不要忘记在Comment模型中添加has_one:notification)。
comments = Comment.joins(:notification).where(:notifications => { :target_user_id => current_user.id })

请注意,这里使用的 joins 生成了一个 INNER JOIN,因此您可以正确地排除任何没有通知的评论(因为某些通知可能已被用户删除)。
接下来,您需要按其可评论对象对这些评论进行分组,以便为每个可评论对象创建可展开列表。
@comment_groups = comments.group_by { |c| "#{c.commentable_type}#{c.commentable_id}"}

这将生成一个哈希值,例如

`{ 'Photo8' => [comment1, comment2, ...], 'Event3' => [comment1, ...], ... }`

现在你可以在你的视图中使用它了。

some_page_showing_comment_notifications.html.erb 中。

...
<ul>
  <% @comment_groups.each do |group, comments| %>
    <li>
      <%= render 'comment_group', :comments => comments, :group => group %>
    </li>
  <% end %>
</ul>
...

In _comment_group.html.erb

<div>
  <% case comments.length %>
    <% when 1 %>
      <%= comments[0].commenter.name %> commented on your <%= comment[0].commentable_type.downcase %>.
    <% when 2 %>
      <%= comments[0].commenter.name %> and <%= comments[1].commenter.name %> commented on your <%= comment[0].commentable_type.downcase %>.
    <% when 3 %>
      <%= comments[0].commenter.name %>, <%= comments[1].commenter.name %> and <%= comments[2].commenter.name %> commented on your <%= comment[0].commentable_type.downcase %>
    <% else %>
      <%= render 'long_list_comments', :comments => comments, :group => group %>
  <% end %>
</div>

在IT相关领域,翻译为中文:

_long_list_comments.html.erb文件中。

<div>
  <%= comments[0].commenter.name %> and <%= comments.length-1 %> others commented on your <%= comments[0].commentable_type %>.
  <%= button_tag "+", class: "expand-comments-button", id: "#{group}-button" %>
</div>
<%= content_tag :ul, class: "expand-comments-list", id: "#{group}-list" do %>
  <li>
    <% comments.each do |comment| %>
      # render each comment in the same way as before
    <% end %>
  </li>
<% end %>

最后,向 button.expand-comments-button 添加一些 JavaScript 来切换 ul.expand-comments-listdisplay 属性应该是一个简单的问题。每个按钮和列表都有基于评论组键的唯一 ID,因此您可以使每个按钮展开正确的列表。

嗨!很抱歉回复晚了。这将从对象本身(评论)开始,但我不认为我已经提到过,还有其他需要通知的人。实际上我之前做了一点东西,你能看看它是否可以吗(发布为新答案)? - index

0

所以,在第二个答案发布之前,我已经做了一些小东西,但是因为太忙了,没能在这里发表。而且我还在研究我是否做对了,它是否可扩展或整体性能如何。我很想听听你们对我实现方式的所有想法、建议和评论。以下是我的做法:

首先,我创建了典型的多态表格。

# migration
create_table :activities do |t|
  t.references :receiver
  t.references :user
  t.references :notifiable
  t.string :notifiable_type  # 
  t.string :type             # Type of notification like 'comment' or 'another type'
  ...
end

# user.rb
class User < ActiveRecord::Base
  has_many :notifications, :foreign_key => :receiver_id, :dependent => :destroy
end

# notification.rb
class Notification < ActiveRecord::Base
  belongs_to :receiver, :class_name => 'User'
  belongs_to :user
  belongs_to :notifiable, :polymorphic => true

  COMMENT = 'Comment'
  ANOTHER_TYPE = 'Another Type'
end

所以就是这样。然后插入通常是当用户执行某种类型的通知时。那么我在psql中发现的新东西是ARRAY_AGG,它是一种聚合函数,将某个字段分组为一个新字段作为数组(但在rails中变成字符串)。所以现在我获取记录的方式是这样的:

# notification.rb

scope :aggregated,
  select('type, notifiable_id, notifiable_type,
    DATE(notifications.created_at),
    COUNT(notifications.id) AS count,
    ARRAY_AGG(users.name) AS user_names,
    ARRAY_AGG(users.image) as user_images,
    ARRAY_AGG(id) as ids').
  group('type, notifiable_id, notifiable_type, DATE(notifications.created_at)').
  order('DATE(notifications.created_at) DESC').
  joins(:user)

这将输出类似于:

type     | notifiable_id | notifiable_type | date | count | user_names                     | user_images                  | id
"Comment"| 3             | "Status"        |[date]| 3     | "['first', 'second', 'third']" |"['path1', 'path2', 'path3']" |"[1, 2, 3]"

然后在我的通知模型中,我再次有这些方法,基本上只是将它们放回到一个数组中并删除非唯一项(这样某个聚合通知中不会显示两次相同的名称):

# notification.rb

def array_of_aggregated_users
  self.user_names[1..-2].split(',').uniq
end

def array_of_aggregated_user_images
  self.user_images[1..-2].split(',').uniq
end

然后在我的视图中,我有类似这样的东西

# index.html.erb
<% @aggregated_notifications.each do |agg_notif| %>
  <% 
    all_names = agg_notif.array_of_aggregated_users
    all_images = agg_notif.array_of_aggregated_user_images
  %>

  <img src="<%= all_images[0] %>" />

  <% if all_names.length == 1 %>
    <%= all_names[0] %>
  <% elsif all_names.length == 2 %>
    <%= all_names[0] %> and <%= all_names[1] %>
  <% elsif all_names.length == 3 %>
    <%= all_names[0] %>, <%= all_names[1] %> and <%= all_names[2] %>
  <% else %>
    <%= all_names[0] %> and <%= all_names.length - 1 %> others
  <% end %>

  <%= agg_notif.type %> on your <%= agg_notif.notifiable_type %>

  <% if agg_notif.count > 1 %>
    <%= set_collapsible_link # [-/+] %>
  <% else %>
    <%= set_actions(ids[0]) %>
  <% end %>
<% end %>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接