lib/recommendable/rater/recommender.rb
module Recommendable
module Rater
module Recommender
# Get a list of raters that have been found to be the most similar to
# self. They are sorted by the calculated similarity value.
#
# @param [Fixnum] limit the number of users to return (defaults to 10)
# @return [Array] An array of instances of your user class
def similar_raters(limit = 10, offset = 0)
ids = Recommendable.redis.zrevrange(Recommendable::Helpers::RedisKeyMapper.similarity_set_for(id), 0, -1)
ids = sanitize_ids(ids, self.class)
order = ids.map { |id| "#{Recommendable.config.user_class.quoted_table_name}.#{Recommendable.config.user_class.quoted_primary_key} = ? DESC" }.join(', ')
order = self.class.send(:sanitize_sql_for_assignment, [order, *ids])
Recommendable.query(self.class, ids).order(order).limit(limit).offset(offset)
end
# Fetch a list of recommendations for a passed class.
#
# @param [String, Symbol, Class] klass the class from which to get recommendations
# @param [Fixnum] limit the number of recommendations to fetch (defaults to 10)
# @return [Array] a list of things this person's gonna love
def recommended_for(klass, limit = 10, offset = 0)
recommended_set = Recommendable::Helpers::RedisKeyMapper.recommended_set_for(klass, self.id)
return Recommendable.query(klass, []) unless rated_anything? && Recommendable.redis.zcard(recommended_set) > 0
ids = Recommendable.redis.zrevrange(recommended_set, 0, -1, :with_scores => true)
ids = ids.select { |id, score| score > 0 }.map { |pair| pair.first }
ids = sanitize_ids(ids, klass)
order = ids.map { |id| "#{klass.quoted_table_name}.#{klass.quoted_primary_key} = ? DESC" }.join(', ')
order = klass.send(:sanitize_sql_for_assignment, [order, *ids])
Recommendable.query(klass, ids).order(order).limit(limit).offset(offset)
end
private
# Sanitizes ids using klass type mapping
# @private
def sanitize_ids(ids, klass)
ids.map{ |id| klass.new(klass.primary_key => id).send(klass.primary_key) }.compact
end
# Removes an item from a user's set of recommendations
# @private
def unrecommend(obj)
Recommendable.redis.zrem(Recommendable::Helpers::RedisKeyMapper.recommended_set_for(obj.class, id), obj.id)
true
end
# Removes a user from Recommendable. Called internally on a before_destroy hook.
# @private
def remove_from_recommendable!
sets = [] # SREM needed
zsets = [] # ZREM needed
keys = [] # DEL needed
# Remove from other users' similarity ZSETs
zsets += Recommendable.redis.keys(Recommendable::Helpers::RedisKeyMapper.similarity_set_for('*'))
# Remove this user's similarity ZSET
keys << Recommendable::Helpers::RedisKeyMapper.similarity_set_for(id)
# For each ratable class...
Recommendable.config.ratable_classes.each do |klass|
# Remove this user from any class member's liked_by/disliked_by sets
sets += Recommendable.redis.keys(Recommendable::Helpers::RedisKeyMapper.liked_by_set_for(klass, '*'))
sets += Recommendable.redis.keys(Recommendable::Helpers::RedisKeyMapper.disliked_by_set_for(klass, '*'))
# Remove this user's liked/disliked/hidden/bookmarked/recommended sets for the class
keys << Recommendable::Helpers::RedisKeyMapper.liked_set_for(klass, id)
keys << Recommendable::Helpers::RedisKeyMapper.disliked_set_for(klass, id)
keys << Recommendable::Helpers::RedisKeyMapper.hidden_set_for(klass, id)
keys << Recommendable::Helpers::RedisKeyMapper.bookmarked_set_for(klass, id)
keys << Recommendable::Helpers::RedisKeyMapper.recommended_set_for(klass, id)
end
Recommendable.redis.pipelined do |redis|
sets.each { |set| redis.srem(set, id) }
zsets.each { |zset| redis.zrem(zset, id) }
redis.del(*keys)
end
end
end
end
end