Ala Facebook notifications (database implementation)

I am wondering how Facebook is implementing its notification system as I am looking for something similar.

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

I cannot have several notifications recorded in one record, as in the end I will have actions associated with each notification. In addition, in the view, I would like the notifications to appear as extensible lists when a certain number of them exist for a single object.

  • 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 (action)
    • ProtoMan commented on your event (action)
    • Bass commented on your event (action)
    • DrWilly commented on your event (action)

Hooray!

PS I use postgres and rails BTW.

+4
source share
3 answers

There are several ways to implement this. It really depends on what types of notifications you want to cover, and what information you need to collect about the notification in order to show it to the right users (users). If you're looking for a simple design that simply covers notifications about posted comments, you can 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 happens here is that each photo, status, event, etc. there are a lot of comments. A Comment obviously belongs to a :commenter , but also a :commentable , which is either a photo, status, event, or any other model for which you want to allow commenting. Then you have a CommentObserver that will watch your Comment model and do something when something happens to the Comment table. In this case, after creating the Comment observer will create a CommentNotification with the identifier of the comment and the identifier of the user who owns the thing about which the comment is ( comment.commentable.owner.id ). To do this, you need to implement a simple :owner method for each model for which you want to have comments. So, for example, if the comment is a photograph, the owner will be the user who posted the photograph.

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

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

With this scheme, you would β€œwatch” all your notifiables (the models for which you want to create notifications), and do something like the following in the 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 

The only tricky part here is the user_to_notify method. All models that are notifiable will need to somehow implement it depending on the model. For example, wall_post.user_to_notify will simply be the owner of the wall, or like.user_to_notify will be the owner of the thing that you liked. Perhaps you can have several people to notify about this, for example, when notifying all the people marked in the photo when someone notices it.

Hope this helps.

+4
source

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

Providing comment notifications in the form of expandable lists, as you noted in your examples, you first collect comments that have notifications for a target user (do not forget to add has_one :notification to the Comment model).

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

Note that using joins here generates an INNER JOIN , so you correctly exclude any comments that do not have notifications (since some notifications may be deleted by the user).

Then you want to group these comments with your comments so you can create an extensible list for each comment.

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

which will generate a hash for example

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

which you can now use in your presentation.

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 just add javascript to button.expand-comments-button to toggle the display ul.expand-comments-list property. Each button and list has a unique identifier based on the keys of the comment group, so you can expand each correct button on each button.

+1
source

So, I did something a bit before the second answer was sent, but too busy to compose and put it here. And I'm still studying if I did the right thing here, if it scaled, or how it would work in general. I would like to hear all your ideas, suggestions, comments on how I did this. Here:

Therefore, 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 

The way it is. Then the insertion is pretty typical, for example, when the user performs a certain type of notification. So, what's new that I found for psql is ARRAY_AGG , which is an aggregated function that groups a specific field into a new field as an array (but a string when it comes to the rails). So, as I get the entries, 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 again, in my notification model, I have methods that basically just return them to an array and delete non-uniques (so that the name will not appear twice in a specific 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 opinion, 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 %> 
0
source

Source: https://habr.com/ru/post/1436302/


All Articles