4

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

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

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

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

干杯!

PS我正在使用postgres和rails BTW。

4

3 回答 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

这里发生的是每张照片、状态、事件等都有很多评论。AComment显然属于 a ,:commenter但也属于 a :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是“喜欢”的东西的所有者。您甚至可能要通知多个人,例如当有人评论照片时通知照片中所有标记的人。

希望这可以帮助。

于 2012-10-02T06:26:26.367 回答
1

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

要将评论通知呈现为您在示例中提到的可扩展列表,您首先收集具有某些目标用户通知的评论(不要忘记添加has_one :notificationComment模型中)。

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

最后,添加一些 javascript来button.expand-comments-button切换display. ul.expand-comments-list每个按钮和列表都有一个基于评论组键的唯一 ID,因此您可以让每个按钮展开正确的列表。

于 2012-10-04T16:10:53.573 回答
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 %>
于 2012-10-30T11:42:19.090 回答