FeedBunch-app/lib/subscriptions_manager.rb
# frozen_string_literal: true
##
# This class has methods related to managing users' subscriptions to feeds.
class SubscriptionsManager
##
# Add a new subscription of a user to a feed. The cached count of unread entries will be
# initialized to the current number of entries in the feed, thanks to various model callbacks.
#
# Receives as arguments the suscribing user and the feed to which he's to be subscribed.
#
# If the user is already subscribed to the feed, an AlreadySubscribedError is raised.
def self.add_subscription(feed, user)
check_user_unsubscribed feed, user
Rails.logger.info "subscribing user #{user.id} - #{user.email} to feed #{feed.id} - #{feed.fetch_url}"
feed_subscription = FeedSubscription.new feed_id: feed.id
user.feed_subscriptions << feed_subscription
return nil
end
##
# Unsubscribes a user from a feed.
#
# Receives as argument the feed to unsubscribe, and the user who is unsubscribing.
#
# If the user is not subscribed to the feed, a NotSubscribedError is raised.
def self.remove_subscription(feed, user)
if !user_subscribed? feed, user
Rails.logger.warn "User #{user.id} - #{user.id} tried to unsubscribe from feed #{feed.id} - #{feed.fetch_url} but he is not subscribed"
raise NotSubscribedError.new
end
feed_subscription = FeedSubscription.find_by feed_id: feed.id, user_id: user.id
Rails.logger.info "unsubscribing user #{user.id} - #{user.email} from feed #{feed.id} - #{feed.fetch_url}"
user.feed_subscriptions.delete feed_subscription
return nil
end
##
# Enqueue a job to unsubscribes a user from a feed.
#
# Receives as argument the feed to unsubscribe, and the user who is unsubscribing.
def self.enqueue_unsubscribe_job(feed, user)
if !user_subscribed? feed, user
Rails.logger.warn "User #{user.id} - #{user.id} tried to enqueue job to unsubscribe from feed #{feed.id} - #{feed.fetch_url} but he is not subscribed"
raise NotSubscribedError.new
end
Rails.logger.info "Enqueuing unsubscribe job for user #{user.id} - #{user.email}, feed #{feed.id} - #{feed.fetch_url}"
UnsubscribeUserWorker.perform_async user.id, feed.id
return nil
end
##
# Retrieve the count of unread entries in a feed for a given user. This count is not
# calculated when this method is invoked, but rather it is retrieved from a pre-calculated
# field in the database.
#
# Receives as arguments:
# - feed from which to retrieve the count
# - user for whom the unread entries count is to be retrieved
#
# Returns a positive (or zero) integer with the count.
# If the user is not actually subscribed to the feed, a NotSubscribedError is raised.
def self.feed_unread_count(feed, user)
if !user_subscribed? feed, user
Rails.logger.warn "User #{user.id} - #{user.id} tried to retrieve unread count from feed #{feed.id} - #{feed.fetch_url} but he is not subscribed"
raise NotSubscribedError.new
end
feed_subscription = user.feed_subscriptions.find_by feed_id: feed.id
return feed_subscription.unread_entries
end
##
# Increment the count of unread entries in a feed for a given user.
#
# Receives as arguments:
# - feed which count will be incremented
# - user for which the count will be incremented
# - increment: how much to increment the count. Optional, has default value of 1.
#
# If the user is not actually subscribed to the feed, a NotSubscribedError is raised.
def self.feed_increment_count(feed, user, increment=1)
if !user_subscribed? feed, user
Rails.logger.warn "User #{user.id} - #{user.id} tried to change unread count for feed #{feed.id} - #{feed.fetch_url} but he is not subscribed"
raise NotSubscribedError.new
end
feed_subscription = user.feed_subscriptions.find_by feed_id: feed.id
Rails.logger.debug "Incrementing unread entries count for user #{user.id} - #{user.email}, feed #{feed.id} - #{feed.fetch_url}. Current: #{feed_subscription.unread_entries}, incremented by #{increment}"
feed_subscription.unread_entries += increment
feed_subscription.save!
return nil
end
##
# Decrement the count of unread entries in a feed for a given user.
#
# Receives as arguments:
# - feed which count will be decremented
# - user for which the count will be decremented
# - decrement: how much to decrement the count. Optional, has default value of 1.
#
# If the user is not actually subscribed to the feed, a NotSubscribedError is raised.
def self.feed_decrement_count(feed, user, decrement=1)
self.feed_increment_count feed, user, -decrement
end
##
# Recalculate the count of unread entries in a feed for a given user.
#
# This method counts the unread entries in the feed and updates the unread_entries field
# accordingly. It can be used to ensure the unread_entries field is correct.
#
# Receives as arguments:
# - feed: feed for which the count will be recalculated.
# - user: user for whom the count will be recalculated.
#
# Returns the recalculated count.
#
# This method writes in the database only if necessary (i.e. the currently saved unread_entries does
# not match the calculated count). This avoids unnecessary database writes, which are expensive
# operations.
def self.recalculate_unread_count(feed, user)
Rails.logger.debug "Recalculating unread entries count for feed #{feed.id} - #{feed.title}, user #{user.id} - #{user.email}"
count = EntryState.joins(entry: :feed).where(read: false, user: user, feeds: {id: feed.id}).count
if user.feed_unread_count(feed) != count
Rails.logger.debug "Unread entries count calculated: #{count}, current value #{user.feed_unread_count(feed)}. Updating DB record."
feed_subscription = FeedSubscription.find_by user_id: user.id, feed_id: feed.id
feed_subscription.update unread_entries: count
else
Rails.logger.debug "Unread entries count calculated: #{count}, current value is correct. No need to update DB record."
end
return count
end
#############################
# PRIVATE CLASS METHODS
#############################
##
# Find out if a user is subscribed to a feed.
#
# Receives as arguments the feed and the user to check.
#
# Returns true if the user is subscribed to the feed, false otherwise.
def self.user_subscribed?(feed, user)
if FeedSubscription.exists? feed_id: feed.id, user_id: user.id
return true
else
return false
end
end
private_class_method :user_subscribed?
##
# Check that a user is not subscribed to a feed.
#
# Receives as arguments the feed and user to check.
#
# If the user is subscribed to the feed a NotSubscribedError is raised.
# Otherwise nil is returned.
def self.check_user_unsubscribed (feed, user)
if user_subscribed? feed, user
Rails.logger.warn "User #{user.id} - #{user.id} tried to subscribe to feed #{feed.id} - #{feed.fetch_url} to which he is already subscribed"
raise AlreadySubscribedError.new feed
end
return nil
end
private_class_method :check_user_unsubscribed
end