base.rb 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. # frozen_string_literal: true
  2. class Trends::Base
  3. include Redisable
  4. include LanguagesHelper
  5. class_attribute :default_options
  6. attr_reader :options
  7. # @param [Hash] options
  8. # @option options [Integer] :threshold Minimum amount of uses by unique accounts to begin calculating the score
  9. # @option options [Integer] :review_threshold Minimum rank (lower = better) before requesting a review
  10. # @option options [ActiveSupport::Duration] :max_score_cooldown For this amount of time, the peak score (if bigger than current score) is decayed-from
  11. # @option options [ActiveSupport::Duration] :max_score_halflife How quickly a peak score decays
  12. def initialize(options = {})
  13. @options = self.class.default_options.merge(options)
  14. end
  15. def register(_status)
  16. raise NotImplementedError
  17. end
  18. def add(*)
  19. raise NotImplementedError
  20. end
  21. def refresh(*)
  22. raise NotImplementedError
  23. end
  24. def request_review
  25. raise NotImplementedError
  26. end
  27. def query
  28. Trends::Query.new(key_prefix, klass)
  29. end
  30. def score(id, locale: nil)
  31. redis.zscore([key_prefix, 'all', locale].compact.join(':'), id) || 0
  32. end
  33. def rank(id, locale: nil)
  34. redis.zrevrank([key_prefix, 'allowed', locale].compact.join(':'), id)
  35. end
  36. def currently_trending_ids(allowed, limit)
  37. redis.zrevrange(allowed ? "#{key_prefix}:allowed" : "#{key_prefix}:all", 0, limit.positive? ? limit - 1 : limit).map(&:to_i)
  38. end
  39. protected
  40. def key_prefix
  41. raise NotImplementedError
  42. end
  43. def recently_used_ids(at_time = Time.now.utc)
  44. redis.smembers(used_key(at_time)).map(&:to_i)
  45. end
  46. def record_used_id(id, at_time = Time.now.utc)
  47. redis.sadd(used_key(at_time), id)
  48. redis.expire(used_key(at_time), 1.day.seconds)
  49. end
  50. def score_at_rank(rank)
  51. redis.zrevrange("#{key_prefix}:allowed", 0, rank, with_scores: true).last&.last || 0
  52. end
  53. def replace_items(suffix, items)
  54. tmp_prefix = "#{key_prefix}:tmp:#{SecureRandom.alphanumeric(6)}#{suffix}"
  55. allowed_items = filter_for_allowed_items(items)
  56. redis.pipelined do |pipeline|
  57. items.each { |item| pipeline.zadd("#{tmp_prefix}:all", item[:score], item[:item].id) }
  58. allowed_items.each { |item| pipeline.zadd("#{tmp_prefix}:allowed", item[:score], item[:item].id) }
  59. rename_set(pipeline, "#{tmp_prefix}:all", "#{key_prefix}:all#{suffix}", items)
  60. rename_set(pipeline, "#{tmp_prefix}:allowed", "#{key_prefix}:allowed#{suffix}", allowed_items)
  61. end
  62. end
  63. def filter_for_allowed_items(items)
  64. raise NotImplementedError
  65. end
  66. private
  67. def used_key(at_time)
  68. "#{key_prefix}:used:#{at_time.beginning_of_day.to_i}"
  69. end
  70. def rename_set(pipeline, from_key, to_key, set_items)
  71. if set_items.empty?
  72. pipeline.del(to_key)
  73. else
  74. pipeline.rename(from_key, to_key)
  75. end
  76. end
  77. def skip_review?
  78. Setting.trendable_by_default
  79. end
  80. end