通知ala Facebook(数据库实施)

时间:2012-09-26 07:13:31

标签: ruby-on-rails database facebook database-design notifications

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

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

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

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

干杯!

PS 我正在使用postgres和rails BTW。

3 个答案:

答案 0 :(得分:4)

有很多方法可以实现这一点。这实际上取决于您想要涵盖哪些类型的通知以及您需要收集哪些有关通知的信息,以便将其显示给正确的用户。如果您正在寻找一个简单的设计,其中只包含有关已发布评论的通知,您可以使用polymorphic associationsobserver 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之后,观察者将创建一个CommentNotification,其中包含注释的id和拥有该注释所关注事物的用户的id({{1} })。这将要求您为要为其注释的每个模型实现一个简单的方法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将是'喜欢'的东西。您甚至可能有多个人通知,例如当有人对照片进行评论时通知所有标记在照片中的人。

希望这有帮助。

答案 1 :(得分:1)

我决定把这个作为另一个答案发布,因为第一个答案是荒谬的。

将评论通知呈现为您在自己的笔记中记下的可扩展列表 例如,您首先收集包含某些目标用户通知的评论(不要忘记将has_one :notification添加到Comment模型中)。

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

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

_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以切换display的{​​{1}}属性应该是一件简单的事情。每个按钮和列表都有一个基于注释组键的唯一ID,因此您可以使每个按钮展开正确的列表。

答案 2 :(得分: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 %>