类似于Facebook的通知(数据库实现)。[英] Notifications ala Facebook (database implementation)

本文是小编为大家收集整理的关于类似于Facebook的通知(数据库实现)。的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

我想知道Facebook如何实现他们的通知系统,因为我想做类似的事情.

  • Foobar评论了您的状态
  • red1,green2和blue3对您的照片评论
  • Megaman和其他5个评论您的活动

我不能将多个通知写入单个记录中,因为最终我将与每个通知相关联.另外,在视图的视图中,我希望在某个主题存在某些数字时将通知作为可扩展列表呈现.

  • Foobar评论了您的状态(动作)
  • red1,green2和pink5对您的照片评论[+]
  • Megaman和其他3个评论您的活动[ - ]
    • Megaman对您的活动(动作)评论
    • Protoman对您的活动(动作)评论
    • 低音评论您的活动(动作)
    • Drwilly对您的活动(动作)评论

欢呼!

ps 我正在使用Postgres和Rails btw.

推荐答案

有多种实施方法.这实际上取决于您要涵盖哪些类型的通知以及需要收集有关通知的信息以将其显示给正确的用户.如果您正在寻找仅涵盖发布评论的通知的简单设计,则可以使用 observer callbacks :

:

:

:

:

:

:

:

:

:

:

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之后,观察者将创建A CommentNotification具有注释的ID和拥有该评论有关的内容的用户的ID(comment.commentable.owner.id).这将要求您为要注释的每个模型实现一个简单的方法:owner.因此,例如,如果可评论的是照片,所有者将是发布照片的用户.

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

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方法.所有notifiable的模型都必须根据模型是什么,以某种方式实现.例如,wall_post.user_to_notify只是墙的所有者,或者like.user_to_notify将是"喜欢"的东西的所有者.您甚至可能有多个人要通知,例如,当有人对照片发表评论时,通知所有被标记的人时.

希望这会有所帮助.

其他推荐答案

我决定将其发布为另一个答案,因为第一个

在您在您中注意到的评论通知作为可扩展列表 示例,您首先收集有关某些目标用户通知的评论(不要忘记将has_one :notification添加到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, ...], ... }`

您现在可以在视图中使用.

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

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

最后,将一些JavaScript添加到button.expand-comments-button以切换display属性ul.expand-comments-list的属性应该是一个简单的事情.每个按钮和列表都具有基于注释组键的唯一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 将某个字段分组到新字段中的汇总函数作为数组(但是在到达导轨时字符串).因此,我现在如何获得记录是这样的:

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

本文地址:https://www.itbaoku.cn/post/597687.html

问题描述

I am wondering how Facebook implements their notifications system as I'm looking to do something similar.

  • FooBar commented on your status
  • Red1, Green2 and Blue3 commented on your photo
  • MegaMan and 5 others commented on your event

I can't have multiple notifications written into a single record, as eventually I will have actions associated with each notification. Also, in the view I'd like notifications to be rendered as expandable lists when a certain number of them exist for a single subject.

  • FooBar commented on your status (actions)
  • Red1, Green2 and Pink5 commented on your photo [+]
  • MegaMan and 3 others commented on your event [-]
    • MegaMan commented on your event (actions)
    • ProtoMan commented on your event (actions)
    • Bass commented on your event (actions)
    • DrWilly commented on your event (actions)

Cheers!

PS I am using postgres and rails BTW.

推荐答案

There are a number of ways to go about implementing this. It really depends on what kinds of notifications you want to cover and what information you need to collect about the notification to show it to the right user(s). If you are looking for a simple design that just covers notifications about posted comments, you could use a combination of polymorphic associations and observer callbacks:

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

What's happening here is that each photo, status, event etc. has many comments. A Comment obviously belongs to a :commenter but also to a :commentable, which is either a photo, status, event or any other model that you want to allow comments for. You then have a CommentObserver that will observe your Comment model and do something whenever something happens with the Comment table. In this case, after a Comment is created, the observer will create a CommentNotification with the id of the comment and the id of the user who owns the thing that the comment is about (comment.commentable.owner.id). This would require that you implement a simple method :owner for each model you want to have comments for. So, for example, if the commentable is a photo, the owner would be the user who posted the photo.

This basic design should be enough to get you started, but note that if you want to create notifications for things other than comments, you could extend this design by using a polymorphic association in a more general Notification model.

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

With this design, you would then 'observe' all your notifiables (models that you want to create notifications for) and do something like the following in your after_create callback:

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

The only tricky part here is the user_to_notify method. All models that are notifiable would have to implement it in some way depending on what the model is. For example, wall_post.user_to_notify would just be the owner of the wall, or like.user_to_notify would be the owner of the thing that was 'liked'. You might even have multiple people to notify, like when notifying all the people tagged in a photo when someone comments on it.

Hope this helps.

其他推荐答案

I decided to post this as another answer because the first was getting ridiculously long.

To render the comment notifications as expandable lists as you note in your examples, you first collect the comments that have notifications for some target user (don't forget to add has_one :notification to the Comment model).

comments = Comment.joins(:notification).where(:notifications => { :target_user_id => current_user.id })

Note that the use of joins here generates an INNER JOIN, so you correctly exclude any comments that don't have notifications (as some notifications might have been deleted by the user).

Next, you want to group these comments by their commentables so that you can create an expandable list for each commentable.

@comment_groups = comments.group_by { |c| "#{c.commentable_type}#{c.commentable_id}"}

which will generate a hash like

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

that you can now use in your view.

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

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

Finally, it should be a simple matter to add some javascript to button.expand-comments-button to toggle the display property of ul.expand-comments-list. Each button and list has a unique id based on the comment group keys, so you can make each button expand the correct list.

其他推荐答案

So I have kinda made a little something before the second answer was posted but got a bit too busy to compose and put it here. And I'm still studying if I did the right thing here, if it would scale or how it would perform overall. I would like to hear all your ideas, suggestions, comments to the way I have implemented this. Here goes:

So I first created the tables as typical polymorphic tables.

# 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

So it's just this. Then inserts quite typically like when a user does a certain type of notification. So what's new that I found for psql is ARRAY_AGG which is an aggregate function that groups a certain field into a new field as an array (but string when it gets to rails). So how I'm getting the records are now like this:

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

This outputs something like:

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]"

And then in my notifications model again, I have this methods which basically just puts them back to an array and removes the non-uniques (so that a name won't be displayed twice in a certain aggregated notification):

# 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

Then in my view I have something like this

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