Class: Weathercock::Scorer

Inherits:
Object
  • Object
show all
Defined in:
lib/weathercock/scorer.rb

Constant Summary collapse

BUCKET_COUNT =
90
BUCKET_TTLS =
{
  hours: BUCKET_COUNT * 3600,
  days: BUCKET_COUNT * 86400,
  months: BUCKET_COUNT * 30 * 86400
}.freeze

Instance Method Summary collapse

Constructor Details

#initialize(klass:) ⇒ Scorer

Returns a new instance of Scorer.



13
14
15
16
# File 'lib/weathercock/scorer.rb', line 13

def initialize(klass:)
  @redis = Weathercock.config.redis
  @key_builder = KeyBuilder.new(namespace: Weathercock.config.namespace, klass: klass)
end

Instance Method Details

#hit(id, event, increment: 1) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/weathercock/scorer.rb', line 18

def hit(id, event, increment: 1)
  now = Time.now
  base = @key_builder.base(event)

  @redis.pipelined do |p|
    p.call("ZINCRBY", @key_builder.total(base), increment, id.to_s)
    BUCKET_TTLS.each do |type, ttl|
      key = @key_builder.bucket(base, type, now)
      p.call("ZINCRBY", key, increment, id.to_s)
      p.call("EXPIRE", key, ttl)
    end
  end
end

#hit_count(id, event, **window) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
# File 'lib/weathercock/scorer.rb', line 45

def hit_count(id, event, **window)
  base = @key_builder.base(event)
  if window.empty?
    score = @redis.call("ZSCORE", @key_builder.total(base), id.to_s)
    return score ? score.to_i : 0
  end

  dest = union(event, window)
  score = @redis.call("ZSCORE", dest, id.to_s)
  score ? score.to_i : 0
end

#hit_counts(event, ids:, **window) ⇒ Object



73
74
75
76
77
78
# File 'lib/weathercock/scorer.rb', line 73

def hit_counts(event, ids:, **window)
  base = @key_builder.base(event)
  dest = window.empty? ? @key_builder.total(base) : union(event, window)
  scores = @redis.call("ZMSCORE", dest, *ids.map(&:to_s))
  ids.zip(scores).to_h { |id, score| [id.to_s, (score || "0").to_i] }
end

#rank(id, event, **window) ⇒ Object



66
67
68
69
70
71
# File 'lib/weathercock/scorer.rb', line 66

def rank(id, event, **window)
  base = @key_builder.base(event)
  dest = window.empty? ? @key_builder.total(base) : union(event, window)
  r = @redis.call("ZREVRANK", dest, id.to_s)
  r ? r + 1 : nil
end

#remove_hits(id, event) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/weathercock/scorer.rb', line 32

def remove_hits(id, event)
  base = @key_builder.base(event)

  @redis.pipelined do |p|
    p.call("ZREM", @key_builder.total(base), id.to_s)
    BUCKET_TTLS.each_key do |type|
      @key_builder.window_keys(base, type, BUCKET_COUNT).each do |key|
        p.call("ZREM", key, id.to_s)
      end
    end
  end
end

#top(event, limit:, decay_factor: nil, **window) ⇒ Object



57
58
59
60
61
62
63
64
# File 'lib/weathercock/scorer.rb', line 57

def top(event, limit:, decay_factor: nil, **window)
  base = @key_builder.base(event)
  stop = limit ? limit - 1 : -1
  return @redis.call("ZRANGE", @key_builder.total(base), 0, stop, "REV") if window.empty?

  dest = union(event, window, decay_factor: decay_factor)
  @redis.call("ZRANGE", dest, 0, stop, "REV")
end