# frozen_string_literal: true module ActsAsFavoritor module Favoritor def self.included(base) base.extend(ClassMethods) end module ClassMethods def acts_as_favoritor has_many :favorites, as: :favoritor, dependent: :destroy include ActsAsFavoritor::Favoritor::InstanceMethods include ActsAsFavoritor::FavoritorLib return unless ActsAsFavoritor.configuration&.cache serialize :favoritor_score, Hash serialize :favoritor_total, Hash end end module InstanceMethods # Returns true if this instance has favorited the object passed as an # argument. def favorited?(favoritable, options = {}) if options.key?(:multiple_scopes) == false options[:parameter] = favoritable validate_scopes(__method__, options) elsif options[:multiple_scopes] results = {} options[:scope].each do |scope| results[scope] = Favorite.unblocked.send(scope + '_list') .for_favoritor(self) .for_favoritable(favoritable).count .positive? end results else Favorite.unblocked.send(options[:scope] + '_list').for_favoritor(self) .for_favoritable(favoritable).count.positive? end end # Returns true if this instance has blocked the object passed as an # argument. def blocked?(favoritable, options = {}) if options.key?(:multiple_scopes) == false options[:parameter] = favoritable validate_scopes(__method__, options) elsif options[:multiple_scopes] results = {} options[:scope].each do |scope| results[scope] = Favorite.blocked.send(scope + '_list') .for_favoritor(self) .for_favoritable(favoritable).count .positive? end results else Favorite.blocked.send(options[:scope] + '_list').for_favoritor(self) .for_favoritable(favoritable).count.positive? end end # Returns true if this instance has favorited the object passed as an # argument. Returns nil if this instance has not favorited the object # passed as an argument. Returns false if this instance has blocked the # object passed as an argument. def favorited_type(favoritable, options = {}) if options.key?(:multiple_scopes) == false options[:parameter] = favoritable validate_scopes(__method__, options) elsif options[:multiple_scopes] results = {} options[:scope].each do |scope| if Favorite.unblocked.send(scope + '_list').for_favoritor(self) .for_favoritable(favoritable).count.positive? results[scope] = true elsif Favorite.blocked.send(scope + '_list').for_favoritor(self) .for_favoritable(favoritable).count.positive? results[scope] = false else results[scope] = nil end end results elsif Favorite.unblocked.send(options[:scope] + '_list') .for_favoritor(self).for_favoritable(favoritable).count .positive? true elsif Favorite.blocked.send(options[:scope] + '_list') .for_favoritor(self).for_favoritable(favoritable).count .positive? false end end # Returns the number of objects this instance has favorited. def favorites_count(options = {}) if options.key?(:multiple_scopes) == false validate_scopes(__method__, options) elsif options[:multiple_scopes] results = {} options[:scope].each do |scope| results[scope] = Favorite.unblocked.send(scope + '_list') .for_favoritor(self).count end results else Favorite.unblocked.send(options[:scope] + '_list') .for_favoritor(self).count end end # Creates a new favorite record for this instance to favorite the passed # object. Does not allow duplicate records to be created. def favorite(favoritable, options = {}) if options.key?(:multiple_scopes) == false options[:parameter] = favoritable validate_scopes(__method__, options) elsif options[:multiple_scopes] results = {} options[:scope].each do |scope| if ActsAsFavoritor.configuration.cache favoritor_score[scope] = (favoritor_score[scope] || 0) + 1 favoritor_total[scope] = (favoritor_total[scope] || 0) + 1 save! favoritable.favoritable_score[scope] = (favoritable.favoritable_score[scope] || 0) + 1 favoritable.favoritable_total[scope] = (favoritable.favoritable_total[scope] || 0) + 1 favoritable.save! end next unless self != favoritable && scope != 'all' params = { favoritable_id: favoritable.id, favoritable_type: parent_class_name(favoritable), scope: scope } results[scope] = favorites.where(params).first_or_create! end results else if ActsAsFavoritor.configuration.cache favoritor_score[options[:scope]] = (favoritor_score[options[:scope]] || 0) + 1 favoritor_total[options[:scope]] = (favoritor_total[options[:scope]] || 0) + 1 save! favoritable.favoritable_score[options[:scope]] = (favoritable.favoritable_score[options[:scope]] || 0) + 1 favoritable.favoritable_total[options[:scope]] = (favoritable.favoritable_total[options[:scope]] || 0) + 1 favoritable.save! end if self != favoritable && options[:scope] != 'all' params = { favoritable_id: favoritable.id, favoritable_type: parent_class_name(favoritable), scope: options[:scope] } favorites.where(params).first_or_create! end end end # Deletes the favorite record if it exists. def remove_favorite(favoritable, options = {}) if options.key?(:multiple_scopes) == false options[:parameter] = favoritable validate_scopes(__method__, options) elsif options[:multiple_scopes] results = {} options[:scope].each do |scope| if ActsAsFavoritor.configuration.cache favoritor_score[scope] = favoritor_score[scope] - 1 unless favoritor_score[scope].positive? favoritor_score.delete(scope) end save! favoritable.favoritable_score[scope] = favoritable.favoritable_score[scope] - 1 unless favoritable.favoritable_score[scope].positive? favoritable.favoritable_score.delete(scope) end favoritable.save! end favorite = get_favorite( favoritable, scope: scope, multiple_scopes: false ) results[scope] = favorite.destroy if favorite end results else if ActsAsFavoritor.configuration.cache favoritor_score[options[:scope]] = favoritor_score[options[:scope]] - 1 unless favoritor_score[options[:scope]].positive? favoritor_score.delete(options[:scope]) end save! favoritable.favoritable_score[options[:scope]] = favoritable.favoritable_score[options[:scope]] - 1 unless favoritable.favoritable_score[options[:scope]].positive? favoritable.favoritable_score.delete(options[:scope]) end favoritable.save! end favorite = get_favorite( favoritable, scope: options[:scope], multiple_scopes: false ) favorite&.destroy end end # returns the favorite records to the current instance def favorites_scoped(options = {}) if options.key?(:multiple_scopes) == false validate_scopes(__method__, options) elsif options[:multiple_scopes] results = {} options[:scope].each do |scope| results[scope] = favorites.unblocked.send(scope + '_list') .includes(:favoritable) end results else favorites.unblocked.send(options[:scope] + '_list') .includes(:favoritable) end end # Returns the favorite records related to this instance by type. def favorites_by_type(favoritable_type, options = {}) if options.key?(:multiple_scopes) == false options[:parameter] = favoritable_type validate_scopes(__method__, options) elsif options[:multiple_scopes] results = {} options[:scope].each do |scope| favorites_scope = favorites_scoped( scope: scope, multiple_scopes: false ).for_favoritable_type(favoritable_type) results[scope] = apply_options_to_scope( favorites_scope, options ) end results else favorites_scope = favorites_scoped( scope: options[:scope], multiple_scopes: false ).for_favoritable_type(favoritable_type) apply_options_to_scope( favorites_scope, options ) end end # Returns the favorite records related to this instance with the # favoritable included. def all_favorites(options = {}) if options.key?(:multiple_scopes) == false validate_scopes(__method__, options) elsif options[:multiple_scopes] results = {} options[:scope].each do |scope| favorites_scope = favorites_scoped( scope: scope, multiple_scopes: false ) results[scope] = apply_options_to_scope( favorites_scope, options ) end results else favorites_scope = favorites_scoped( scope: options[:scope], multiple_scopes: false ) apply_options_to_scope( favorites_scope, options ) end end # Returns the actual records which this instance has favorited. def all_favorited(options = {}) if options.key?(:multiple_scopes) == false validate_scopes(__method__, options) elsif options[:multiple_scopes] results = {} options[:scope].each do |scope| results[scope] = all_favorites(options).collect(&:favoritable) end results else all_favorites(options).collect(&:favoritable) end end # Returns the actual records of a particular type which this record has # favorited. def favorited_by_type(favoritable_type, options = {}) if options.key?(:multiple_scopes) == false options[:parameter] = favoritable_type validate_scopes(__method__, options) elsif options[:multiple_scopes] results = {} options[:scope].each do |scope| favoritables = favoritable_type.constantize.joins(:favorited) favoritables = favoritables.where( 'favorites.blocked': false, 'favorites.favoritor_id': id, 'favorites.favoritor_type': parent_class_name(self), 'favorites.favoritable_type': favoritable_type, 'favorites.scope': scope ) if options.key?(:limit) favoritables = favoritables.limit options[:limit] end if options.key?(:includes) favoritables = favoritables.includes options[:includes] end results[scope] = favoritables end results else favoritables = favoritable_type.constantize.joins(:favorited) favoritables = favoritables.where( 'favorites.blocked': false, 'favorites.favoritor_id': id, 'favorites.favoritor_type': parent_class_name(self), 'favorites.favoritable_type': favoritable_type, 'favorites.scope': options[:scope] ) if options.key? :limit favoritables = favoritables.limit options[:limit] end if options.key? :includes favoritables = favoritables.includes options[:includes] end favoritables end end def favorited_by_type_count(favoritable_type, options = {}) if options.key?(:multiple_scopes) == false options[:parameter] = favoritable_type validate_scopes(__method__, options) elsif options[:multiple_scopes] results = {} options[:scope].each do |scope| results[scope] = favorites.unblocked .send(scope + '_list') .for_favoritable_type(favoritable_type) .count end results else favorites.unblocked.send(options[:scope] + '_list') .for_favoritable_type(favoritable_type).count end end # Allows magic names on favorited_by_type # e.g. favorited_users == favorited_by_type 'User' # Allows magic names on favorited_by_type_count # e.g. favorited_users_count == favorited_by_type_count 'User' def method_missing(method, *args) if method.to_s[/favorited_(.+)_count/] favorited_by_type_count $1.singularize.classify elsif method.to_s[/favorited_(.+)/] favorited_by_type $1.singularize.classify elsif ActsAsFavoritor.configuration.cache && method.to_s[/favoritor_(.+)_score/] favoritor_score[$1.singularize.classify] elsif ActsAsFavoritor.configuration.cache && method.to_s[/favoritor_(.+)_total/] favoritor_total[$1.singularize.classify] else super end end def respond_to_missing?(method, include_private = false) super || method.to_s[/favorited_(.+)_count/] || method.to_s[/favorited_(.+)/] end # Returns a favorite record for the current instance and favoritable # object. def get_favorite(favoritable, options = {}) if options.key?(:multiple_scopes) == false options[:parameter] = favoritable validate_scopes(__method__, options) elsif options[:multiple_scopes] results = {} options[:scope].each do |scope| results[scope] = favorites.unblocked.send(scope + '_list') .for_favoritable(favoritable).first end results else favorites.unblocked.send(options[:scope] + '_list') .for_favoritable(favoritable).first end end def blocks(options = {}) if options.key?(:multiple_scopes) == false validate_scopes(__method__, options) elsif options[:multiple_scopes] results = {} options[:scope].each do |scope| blocked_favoritors_scope = favoritables_scoped( scope: scope, multiple_scopes: false ).blocked blocked_favoritors_scope = apply_options_to_scope( blocked_favoritors_scope, options ) results[scope] = blocked_favoritors_scope.to_a .collect(&:favoritable) end results else blocked_favoritors_scope = favoritors_scoped( scope: options[:scope], multiple_scopes: false ).blocked blocked_favoritors_scope = apply_options_to_scope( blocked_favoritors_scope, options ) blocked_favoritors_scope.to_a.collect(&:favoritable) end end def block(favoritable, options = {}) if options.key?(:multiple_scopes) == false options[:parameter] = favoritable validate_scopes(__method__, options) elsif options[:multiple_scopes] results = {} options[:scope].each do |scope| favorite = get_favorite( favoritable, scope: scope, multiple_scopes: false ) if favorite results[scope] = block_existing_favorite( favoritable, scope: scope, multiple_scopes: false ) else results[scope] = block_future_favorite( favoritable, scope: scope, multiple_scopes: false ) end end results else favorite = get_favorite( favoritable, scope: options[:scope], multiple_scopes: false ) if favorite block_existing_favorite( favoritable, scope: options[:scope], multiple_scopes: false ) else block_future_favorite( favoritable, scope: options[:scope], multiple_scopes: false ) end end end def unblock(favoritable, options = {}) if options.key?(:multiple_scopes) == false options[:parameter] = favoritable validate_scopes(__method__, options) elsif options[:multiple_scopes] results = {} options[:scope].each do |scope| results[scope] = get_favorite( favoritable, scope: scope, multiple_scopes: false )&.update_attribute :blocked, false end results else get_favorite( favoritable, scope: options[:scope], multiple_scopes: false )&.update_attribute :blocked, false end end def blocked_favoritables_count(options = {}) if options.key?(:multiple_scopes) == false validate_scopes(__method__, options) elsif options[:multiple_scopes] results = {} options[:scope].each do |scope| results[scope] = favorites.blocked.send(scope + '_list').count end results else favorites.blocked.send(options[:scope] + '_list').count end end private def block_future_favorite(favoritable, options = {}) if options.key?(:multiple_scopes) == false options[:parameter] = favoritable validate_scopes(__method__, options) elsif options[:multiple_scopes] results = {} options[:scope].each do |scope| results[scope] = Favorite.create( favoritable: favoritable, favoritor: self, blocked: true, scope: scope ) end results else Favorite.create( favoritable: favoritable, favoritor: self, blocked: true, scope: options[:scope] ) end end def block_existing_favorite(favoritable, options = {}) if options.key?(:multiple_scopes) == false options[:parameter] = favoritable validate_scopes(__method__, options) elsif options[:multiple_scopes] results = {} options[:scope].each do |scope| results[scope] = get_favorite( favoritable, scope: scope, multiple_scopes: false ).block! end results else get_favorite( favoritable, scope: options[:scope], multiple_scopes: false ).block! end end end end end