Thato Sello
Thato Sello

Reputation: 61

How to receive one in app notification per user following you?

I have recently finished implementing an in-app notification, albeit there is a huge flaw to the implementation. As much as it is quite feasible when sending an activation message to all users having successfully activated their account it gets a bit complicated when one user follows an end user. When the current user follows and unfollows a few times, the end user gets multiple notifications from the current user. How does one destroy a notification when the current user unfollows the end user, to stop sending multiple "current user started following you" notifications to the end user?

User Model

class User < ActiveRecord::Base
  searchkick word_start: [:name, :surname] 

  has_many :active_relationships,  class_name:  "Relationship",
                                   foreign_key: "follower_id",
                                   dependent:   :destroy
  has_many :passive_relationships, class_name:  "Relationship",
                                   foreign_key: "followed_id",
                                   dependent:   :destroy
  has_many :following, through: :active_relationships,  source: :followed
  has_many :followers, through: :passive_relationships, source: :follower
  has_many :notifications, foreign_key: :recipient_id 

...

  # Follows a user.
  def follow(other_user)
    following << other_user
  end

  # Unfollows a user.
  def unfollow(other_user)
    following.delete(other_user)
  end

  # Returns true if the current user is following the other user.
  def following?(other_user)
    following.include?(other_user)
  end

...

Relationship model

 class Relationship < ActiveRecord::Base

    belongs_to :follower, class_name: "User"
    belongs_to :followed, class_name: "User"
    validates  :follower_id, presence: true
    validates  :followed_id, presence: true

 end

Notification Model

class Notification < ActiveRecord::Base

  belongs_to :recipient, class_name: "User"
  belongs_to :actor, class_name: "User"
  belongs_to :notifiable, polymorphic: true

  scope :unread, ->{ where(read_at: nil) }
  scope :recent, ->{ order(created_at: :desc).limit(5) }

end

Relationship Controller

 class RelationshipsController < ApplicationController
 before_action :logged_in_user

  def create 
    @user = User.find(params[:followed_id])
    current_user.follow(@user)  
    Notification.create(recipient: @user, actor: current_user, action: "following", notifiable: @user)
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end

  def destroy
    @user = Relationship.find(params[:id]).followed
    current_user.unfollow(@user)
    # Where the notification should be destroyed
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end
end

Upvotes: 1

Views: 143

Answers (1)

spickermann
spickermann

Reputation: 107037

It depends on how you implemented your relationship model.

I would only send an email when a relation is created. If a user unfollows another user I would not delete that relation, but flag it as unfollowed. If the user decides to follow the same user again, then you just need to remove the flag that marks the relation as non-active. That means you do not create a new relation, therefore you do not need to send an email.

First, you will need to add an active boolean flag to your Relationship model. That can be done with a migration like this:

def change 
  add_column :relationships, :active, :boolean, default: true
end

Then you need to respect the active flag in your associations and scopes:

has_many :following, 
  -> { Relationship.where(active: true) },
  through: :active_relationships,  source: :followed
has_many :followers, 
  -> { Relationship.where(active: true) },
  through: :passive_relationships, source: :follower

Next, change the follow and unfollow methods:

def follow(other_user)
  relationship = Relationship.where(
    followed_id: other_user.id, follower_id: self.id
  ).first

  if relationship.present?
    relationship.update(active: true) 
  else
    following << other_user

    Notification.create(
      action:     'following', 
      actor:      self, 
      notifiable: other_user,
      recipient:  other_user
    )
  end
end

def unfollow(other_user)
  relationship = Relationship.where(
    followed_id: other_user.id, follower_id: self.id
  )

  relationship.update(active: false) if relationship.present?
end

def following?(other_user)
  Relationship.exists?(
    followed_id: other_user.id, follower_id: self.id, active: true   
  )
end

Note that the Notification is created in the follow method now. That allows to simplify the controller:

def create 
  @user = User.find(params[:followed_id])
  current_user.follow(@user)  

  respond_to do |format|
    format.html { redirect_to @user }
    format.js
  end
end

def destroy
  @user = Relationship.find(params[:id]).followed
  current_user.unfollow(@user)

  respond_to do |format|
    format.html { redirect_to @user }
    format.js
  end
end

Upvotes: 2

Related Questions