hummingbird-me/strait

View on GitHub
lib/strait/rule.rb

Summary

Maintainability
A
35 mins
Test Coverage
A
100%
# frozen_string_literal: true

class Strait
  class Rule
    def initialize(name:, rule:, config:)
      @name = name
      @rule = rule
      @config = config
    end

    def call(user)
      period_count_for(user) <= @rule[:count]
    end

    def to_h
      @rule
    end

    private

    # Get the in-period hit count for a user
    def period_count_for(user)
      key = key_for(user)
      # Round the timestamp to get the bucket
      bucket_time = bucket_for(Time.now)
      first_bucket = ((Time.now.to_i - @rule[:period]) / bucket_length).floor

      @config.redis_pool.with do |redis|
        redis.hincrby(key, bucket_time, 1)

        # We need to iterate the list once to total the counts, so we use that one iteration to also
        # find any buckets which should be removed.
        results = redis.hgetall(key).each_with_object(total: 0, delete: []) do |(time, count), out|
          if time.to_i < first_bucket
            out[:delete] << time
          else
            out[:total] += count.to_i
          end
        end

        redis.hdel(key, *results[:delete]) unless results[:delete].empty?
        results[:total]
      end
    end

    def bucket_for(time)
      (time.to_f / bucket_length).floor * bucket_length
    end

    def bucket_length
      @bucket_length ||= @rule[:period] / @rule[:buckets]
    end

    def key_for(user)
      "strait:#{@name}-#{@rule[:count]}/#{@rule[:period]}:#{user}"
    end
  end
end