通知ala Facebook(数据库实施)

我想知道Facebook如何实现他们的通知系统,因为我正在寻找类似的东西。

  • FooBar评论了你的状态
  • Red1,Green2和Blue3对你的照片发表了评论
  • MegaMan和另外5人对您的活动发表了评论

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

  • FooBar评论了你的状态(行动)
  • Red1,Green2和Pink5对你的照片发表了评论[+]
  • MegaMan和另外3人对您的活动发表了评论[ – ]
    • MegaMan评论了你的活动(行动)
    • ProtoMan评论了您的活动(行动)
    • 巴斯评论了你的活动(行动)
    • DrWilly评论了您的活动(行动)

干杯!

PS我正在使用postgres和rails BTW。

有很多方法可以实现这一点。 这实际上取决于您想要涵盖哪些类型的通知以及您需要收集哪些有关通知的信息,以便将其显示给正确的用户。 如果您正在寻找一个简单的设计,只包含有关已发布注释的通知,您可以使用多态关联和观察者回调的组合:

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 

这里发生的事情是每张照片,状态,事件等都有很多评论。 Comment显然属于:commenter但也属于:commentable ,可以是照片,状态,事件或您想要允许评论的任何其他模型。 然后,您有一个CommentObserver ,它将观察您的Comment模型,并在Comment表中发生某些事情时执行某些操作。 在这种情况下,在创建Comment之后,观察者将创建一个CommentNotification ,其中包含注释的id和拥有该注释所关注事物的用户的id( comment.commentable.owner.id )。 这将要求您实现一个简单的方法:owner您想要为其注释的每个模型的:owner 。 因此,例如,如果可评论是照片,则所有者将是发布照片的用户。

这个基本设计应该足以让您入门,但请注意,如果您想为注释之外的其他内容创建通知,则可以通过在更通用的Notification模型中使用多态关联来扩展此设计。

 class Notification < ActiveRecord::Base belongs_to :notifiable, :polymorphic => true belongs_to :target_user end 

使用此设计,您将“观察”所有notifiables (您要为其创建通知的模型),并在after_create回调中执行以下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方法。 所有需要notifiable模型都必须以某种方式实现它,具体取决于模型的内容。 例如, wall_post.user_to_notify只是墙的所有者,或者like.user_to_notify将是'喜欢'的东西。 您甚至可能有多个人通知,例如当有人对其进行评论时通知照片中标记的所有人。

希望这可以帮助。

我决定发布这个作为另一个答案,因为第一个是非常漫长的。

要将注释通知呈现为您在示例中记录的可扩展列表,首先要收集包含某些目标用户has_one :notificationComment (不要忘记向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

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

_comment_group.html.erb

 
<% 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 %>

_long_list_comments.html.erb

 
<%= 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" %>
<%= content_tag :ul, class: "expand-comments-list", id: "#{group}-list" do %>
  • <% comments.each do |comment| %> # render each comment in the same way as before <% end %>
  • <% end %>

    最后,将一些javascript添加到button.expand-comments-button以切换ul.expand-comments-listdisplay属性应该是一件简单的事情。 每个按钮和列表都有一个基于注释组键的唯一ID,因此您可以使每个按钮展开正确的列表。

    所以在第二个答案发布之前,我有点做了一些事情,但有点太忙了,无法撰写并把它放在这里。 如果我在这里做正确的事情,如果它会扩展或者它将如何整体表现,我仍然在研究。 我希望听到您对我实施此方法的所有想法,建议和意见。 开始:

    所以我首先将表创建为典型的多态表。

     # 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 %>  <% 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 %>