我想知道Facebook如何实现他们的通知系统,因为我正在寻找类似的东西。
我不能将多个通知写入单个记录,因为最终我将有与每个通知相关联的操作。此外,在视图中,我希望当一个主题存在一定数量的通知时,通知将呈现为可扩展列表。
干杯!
PS 我正在使用postgres和rails BTW。
答案 0 :(得分:4)
有很多方法可以实现这一点。这实际上取决于您想要涵盖哪些类型的通知以及您需要收集哪些有关通知的信息,以便将其显示给正确的用户。如果您正在寻找一个简单的设计,其中只包含有关已发布评论的通知,您可以使用polymorphic associations和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
之后,观察者将创建一个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 %>