Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: Keeping count of users unread notifications

I currently have an activity model that handles a user activity notification system. An activity observer creates a new activity when some action happens (such as a new article being created). Now I want to keep a record of how many of these activity notifications the current user has not seen (similar to the notification jewel on facebook). Every time the user clicks on their notifications link the number should reset to 0 and every notification created should increase the count by 1. Where would I store this data for each user? Would using a session work or is something else better?

like image 929
Matthew Hui Avatar asked Oct 14 '12 07:10

Matthew Hui


2 Answers

There are a couple possible approaches to this: normalized and denormalized. I'll start with normalized:

Approach 1: Normalized

It sounds like you have three models in play here: activities, notifications ("user_activities"), and users. Correct me if I'm wrong in my assumptions here:

  • Activity belongs_to :user and has_many :notifications (one user performs an activity and multiple users get notified of the activity)
  • Notification belongs_to :activity and belongs_to :user and has a "read" attribute / flag
  • User has_many :notifications

In an after_create callback on activity, the model should determine what users need notifications of that activity, and then notify them by creating notification objects for each of them.

On notifications, you can create a class method called unread which specifies a condition that the activity's read flag is false:

def self.unread
  where(:read => false)
end

Then, to access a user's unread notifications count, simply call:

user.notifications.unread.count

When a user views his notifications, call:

user.notifications.unread.update_all(:read => true)

Approach 2: Denormalized

In this approach, whenever an activity is created, it should manually increment a counter for each notified user. You can accomplish this with either:

  • An "unseen_count" attribute on user
  • A key-value pair in a non-relational database (e.g. redis)

In Activity:

def users_to_notify
  # Find a list of users to notify
end

def notify_users(users)
  users.each &:notify
end

def after_create
  notify_users(users_to_notify)
end

In User:

def notify
  update_attributes(:unseen_count => unseen_count + 1)
end

def see_activities
  update_attributes(:unseen_count => 0)
end

The downside to this approach is you've eliminated the Notification model, so a user only has a raw count of notifications, and can't view a detailed list of notifications and their associated activities. You could use a hybrid approach, but remember that it's risky to deal with two sources of truth for unseen notification count.

On a side note: it may make more sense to call notify_users in an observer instead of an after_create directly on the model:

class ActivityObserver < ActiveRecord::Observer
  def after_create(activity)
    activity.users_to_notify.each &:notify
  end
end

If you use the observer, you can remove Activity#notify_users and Activity#after_create.

like image 170
Brendan Benson Avatar answered Oct 16 '22 20:10

Brendan Benson


Please have a look at this gem:
https://github.com/ledermann/unread

It's written by me and handles read/unread status of any ActiveRecord objects in a more performant way.

like image 6
Georg Ledermann Avatar answered Oct 16 '22 19:10

Georg Ledermann