123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105 |
- # frozen_string_literal: true
- class Trends::Base
- include Redisable
- include LanguagesHelper
- class_attribute :default_options
- attr_reader :options
- # @param [Hash] options
- # @option options [Integer] :threshold Minimum amount of uses by unique accounts to begin calculating the score
- # @option options [Integer] :review_threshold Minimum rank (lower = better) before requesting a review
- # @option options [ActiveSupport::Duration] :max_score_cooldown For this amount of time, the peak score (if bigger than current score) is decayed-from
- # @option options [ActiveSupport::Duration] :max_score_halflife How quickly a peak score decays
- def initialize(options = {})
- @options = self.class.default_options.merge(options)
- end
- def register(_status)
- raise NotImplementedError
- end
- def add(*)
- raise NotImplementedError
- end
- def refresh(*)
- raise NotImplementedError
- end
- def request_review
- raise NotImplementedError
- end
- def query
- Trends::Query.new(key_prefix, klass)
- end
- def score(id, locale: nil)
- redis.zscore([key_prefix, 'all', locale].compact.join(':'), id) || 0
- end
- def rank(id, locale: nil)
- redis.zrevrank([key_prefix, 'allowed', locale].compact.join(':'), id)
- end
- def currently_trending_ids(allowed, limit)
- redis.zrevrange(allowed ? "#{key_prefix}:allowed" : "#{key_prefix}:all", 0, limit.positive? ? limit - 1 : limit).map(&:to_i)
- end
- protected
- def key_prefix
- raise NotImplementedError
- end
- def recently_used_ids(at_time = Time.now.utc)
- redis.smembers(used_key(at_time)).map(&:to_i)
- end
- def record_used_id(id, at_time = Time.now.utc)
- redis.sadd(used_key(at_time), id)
- redis.expire(used_key(at_time), 1.day.seconds)
- end
- def score_at_rank(rank)
- redis.zrevrange("#{key_prefix}:allowed", 0, rank, with_scores: true).last&.last || 0
- end
- def replace_items(suffix, items)
- tmp_prefix = "#{key_prefix}:tmp:#{SecureRandom.alphanumeric(6)}#{suffix}"
- allowed_items = filter_for_allowed_items(items)
- redis.pipelined do |pipeline|
- items.each { |item| pipeline.zadd("#{tmp_prefix}:all", item[:score], item[:item].id) }
- allowed_items.each { |item| pipeline.zadd("#{tmp_prefix}:allowed", item[:score], item[:item].id) }
- rename_set(pipeline, "#{tmp_prefix}:all", "#{key_prefix}:all#{suffix}", items)
- rename_set(pipeline, "#{tmp_prefix}:allowed", "#{key_prefix}:allowed#{suffix}", allowed_items)
- end
- end
- def filter_for_allowed_items(items)
- raise NotImplementedError
- end
- private
- def used_key(at_time)
- "#{key_prefix}:used:#{at_time.beginning_of_day.to_i}"
- end
- def rename_set(pipeline, from_key, to_key, set_items)
- if set_items.empty?
- pipeline.del(to_key)
- else
- pipeline.rename(from_key, to_key)
- end
- end
- def skip_review?
- Setting.trendable_by_default
- end
- end
|