class Ratelimitcop

Constants

VERSION

Attributes

interval[R]
name[R]
threshold[R]

Public Class Methods

new(name:, threshold:, interval:, redis: {}, options: {}) click to toggle source
# File lib/ratelimitcop.rb, line 9
def initialize(name:, threshold:, interval:, redis: {}, options: {})
  @name = name
  @threshold = threshold
  @interval = interval
  @bucket_interval = options[:bucket_interval] ||= 5
  @bucket_time_span = options[:bucket_time_span] ||= 600
  @bucket_span = options[:bucket_span] ||= @bucket_interval

  raise InvalidBucketConfigError if @bucket_interval > @bucket_time_span || @bucket_interval < @bucket_span

  @redis ||= Redis::Namespace.new(:limiter, redis: Redis.new(redis))

  @all_buckets_count = (@bucket_time_span / @bucket_span).floor
  @sliding_window_buckets_count = (@bucket_interval.to_f / @bucket_span).floor
end

Public Instance Methods

add(count: 1) click to toggle source
# File lib/ratelimitcop.rb, line 25
def add(count: 1)
  key = [@name, bucket_index].join(':')

  @redis.multi do
    @redis.incrby(key, count)
    @redis.expire(key, @interval)
  end

  nil
end
count() click to toggle source
# File lib/ratelimitcop.rb, line 36
def count
  current_bucket_index = bucket_index

  bucket_indices = @sliding_window_buckets_count.times.map do |i|
    (current_bucket_index - i) % @all_buckets_count
  end

  @redis.multi do
    bucket_indices.map do |i|
      key = [@name, i].join(':')
      @redis.get(key)
    end
  end.map(&:to_i).sum
end
exceeded?() click to toggle source
# File lib/ratelimitcop.rb, line 63
def exceeded?
  count >= @threshold
end
exec_within_threshold() { || ... } click to toggle source
# File lib/ratelimitcop.rb, line 58
def exec_within_threshold
  sleep @bucket_span while exceeded?
  yield
end
execute(&block) click to toggle source
# File lib/ratelimitcop.rb, line 51
def execute(&block)
  add
  exec_within_threshold do
    block.call
  end
end

Private Instance Methods

bucket_index() click to toggle source
# File lib/ratelimitcop.rb, line 69
def bucket_index
  ((Time.now.to_i % @all_buckets_count) / @bucket_span).floor
end