Class: Ratelimit

Inherits:
Object
  • Object
show all
Defined in:
lib/ratelimit.rb,
lib/ratelimit/version.rb

Constant Summary collapse

VERSION =
"1.0.4"

Instance Method Summary collapse

Constructor Details

#initialize(key, options = {}) ⇒ Ratelimit

Create a Ratelimit object.

Parameters:

  • key (String)

    A name to uniquely identify this rate limit. For example, ‘emails’

  • options (Hash) (defaults to: {})

    Options hash

Options Hash (options):

  • :bucket_span (Integer) — default: 600

    Time span to track in seconds

  • :bucket_interval (Integer) — default: 5

    How many seconds each bucket represents

  • :bucket_expiry (Integer) — default: @bucket_span

    How long we keep data in each bucket before it is auto expired. Cannot be larger than the bucket_span.

  • :redis (Redis) — default: nil

    Redis client if you need to customize connection options



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/ratelimit.rb', line 17

def initialize(key, options = {})
  @key = key
  unless options.is_a?(Hash)
    raise ArgumentError.new("Redis object is now passed in via the options hash - options[:redis]")
  end
  @bucket_span = options[:bucket_span] || 600
  @bucket_interval = options[:bucket_interval] || 5
  @bucket_expiry = options[:bucket_expiry] || @bucket_span
  if @bucket_expiry > @bucket_span
    raise ArgumentError.new("Bucket expiry cannot be larger than the bucket span")
  end
  @bucket_count = (@bucket_span / @bucket_interval).round
  if @bucket_count < 3
    raise ArgumentError.new("Cannot have less than 3 buckets")
  end
  @raw_redis = options[:redis]
end

Instance Method Details

#add(subject, count = 1) ⇒ Integer

Add to the counter for a given subject.

Parameters:

  • subject (String)

    A unique key to identify the subject. For example, ‘[email protected]

  • count (Integer) (defaults to: 1)

    The number by which to increase the counter

Returns:

  • (Integer)

    The counter value



41
42
43
44
45
46
47
48
49
50
# File 'lib/ratelimit.rb', line 41

def add(subject, count = 1)
  bucket = get_bucket
  subject = "#{@key}:#{subject}"
  redis.multi do |transaction|
    transaction.hincrby(subject, bucket, count)
    transaction.hdel(subject, (bucket + 1) % @bucket_count)
    transaction.hdel(subject, (bucket + 2) % @bucket_count)
    transaction.expire(subject, @bucket_expiry)
  end.first
end

#count(subject, interval) ⇒ Object

Returns the count for a given subject and interval

Parameters:

  • subject (String)

    Subject for the count

  • interval (Integer)

    How far back (in seconds) to retrieve activity.



56
57
58
59
60
61
62
63
64
65
66
# File 'lib/ratelimit.rb', line 56

def count(subject, interval)
  bucket = get_bucket
  interval = [[interval, @bucket_interval].max, @bucket_span].min
  count = (interval / @bucket_interval).floor
  subject = "#{@key}:#{subject}"

  keys = (0..count - 1).map do |i|
    (bucket - i) % @bucket_count
  end
  return redis.hmget(subject, *keys).inject(0) {|a, i| a + i.to_i}
end

#exceeded?(subject, options = {}) ⇒ Boolean

Check if the rate limit has been exceeded.

Parameters:

  • subject (String)

    Subject to check

  • options (Hash) (defaults to: {})

    Options hash

Options Hash (options):

  • :interval (Integer)

    How far back to retrieve activity.

  • :threshold (Integer)

    Maximum number of actions

Returns:

  • (Boolean)


74
75
76
# File 'lib/ratelimit.rb', line 74

def exceeded?(subject, options = {})
  return count(subject, options[:interval]) >= options[:threshold]
end

#exec_within_threshold(subject, options = {}) { ... } ⇒ Object

Execute a block once the rate limit is within bounds WARNING This will block the current thread until the rate limit is within bounds.

Examples:

Send an email as long as we haven’t send 5 in the last 10 minutes

ratelimit.exec_with_threshold(email, [:threshold => 5, :interval => 600]) do
  send_another_email
  ratelimit.add(email)
end

Parameters:

  • subject (String)

    Subject for this rate limit

  • options (Hash) (defaults to: {})

    Options hash

Options Hash (options):

  • :interval (Integer)

    How far back to retrieve activity.

  • :threshold (Integer)

    Maximum number of actions

Yields:

  • The block to be run



102
103
104
105
106
107
108
109
# File 'lib/ratelimit.rb', line 102

def exec_within_threshold(subject, options = {}, &block)
  options[:threshold] ||= 30
  options[:interval] ||= 30
  while exceeded?(subject, options)
    sleep @bucket_interval
  end
  yield(self)
end

#within_bounds?(subject, options = {}) ⇒ Boolean

Check if the rate limit is within bounds

Parameters:

  • subject (String)

    Subject to check

  • options (Hash) (defaults to: {})

    Options hash

Options Hash (options):

  • :interval (Integer)

    How far back to retrieve activity.

  • :threshold (Integer)

    Maximum number of actions

Returns:

  • (Boolean)


84
85
86
# File 'lib/ratelimit.rb', line 84

def within_bounds?(subject, options = {})
  return !exceeded?(subject, options)
end