【问题标题】:Notifications ala Facebook (database implementation)通知 ala Facebook(数据库实现)
【发布时间】:2012-09-17 18:24:12
【问题描述】:

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

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

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

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

干杯!

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

【问题讨论】:

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


    【解决方案1】:

    有很多方法可以实现这一点。这实际上取决于您要涵盖哪些类型的通知以及您需要收集有关通知的哪些信息以将其显示给正确的用户。如果您正在寻找一个仅涵盖有关已发布 cmets 通知的简单设计,您可以使用 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
    

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

    这个基本设计应该足以让您入门,但请注意,如果您想为除 cmets 之外的事物创建通知,您可以通过在更通用的 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 将是被“喜欢”的东西的所有者。您甚至可能需要通知多个人,例如当有人在照片上出现时通知照片中标记的所有人。

    希望这会有所帮助。

    【讨论】:

    • 这会让我省去很多麻烦,所以我添加一个赏金作为感谢,但我要等到 24 小时之后才能分配它。
    • @Duopixel 非常感谢!如果您需要扩展任何内容,请告诉我。
    • 谢谢。我也设法做到了这一点,但没有观察者部分。我现在只是像 after_create 一样把它放进去。但是我怎样才能显示像我给出的例子一样的东西。比如,发帖用户的前几个名字,说评论?
    • @index 首先,您希望在通知旁边显示哪些“操作”?你的意思是像“删除”、“响应”等吗?
    • 是的。删除和回复(但会转到个人消息,因此我不需要将 cmets 与通知联系起来)和其他一些。
    【解决方案2】:

    我决定将此作为另一个答案发布,因为第一个答案太长了。

    将评论通知呈现为您在 例如,您首先收集对某个目标用户有通知的 cmets(不要忘记将 has_one :notification 添加到 Comment 模型中)。

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

    请注意,此处使用joins 会生成INNER JOIN,因此您可以正确排除任何没有通知的 cmets(因为某些通知可能已被用户删除)。

    接下来,您希望将这些 cmets 按其可评论内容进行分组,以便您可以为每个可评论内容创建一个可展开的列表。

    @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 来切换ul.expand-comments-listdisplay 属性应该是一件简单的事情。每个按钮和列表都有一个基于评论组键的唯一 ID,因此您可以让每个按钮展开正确的列表。

    【讨论】:

    • 嗨!我很抱歉迟到了回复。这将从对象本身(评论)开始,但我认为我没有提到,但还有其他需要通知的。之前其实也做了点小东西,能不能看看是不是没问题(作为新答案贴出来)?
    【解决方案3】:

    所以在发布第二个答案之前,我做了一些事情,但有点太忙了,无法撰写并将其放在这里。而且我仍在研究我是否在这里做了正确的事情,它是否会扩展或整体表现如何。我想听听您对我实施此方法的所有想法、建议和 cmets。如下:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-02-01
      • 2016-10-22
      • 2011-07-18
      • 2018-10-12
      • 2010-12-25
      • 1970-01-01
      • 1970-01-01
      • 2013-08-14
      相关资源
      最近更新 更多