Skip to content

Commit

Permalink
Merge ce6a0dc into d46f442
Browse files Browse the repository at this point in the history
  • Loading branch information
seanxiesx committed May 11, 2015
2 parents d46f442 + ce6a0dc commit 96d473b
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 10 deletions.
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -23,6 +23,10 @@ Check if subject has exceeded limit:

rl.exceeded?(sender)

Query number of seconds until subject is not rate limited (returns 0.0 if subject is not rate limited):

rl.retry_in?(sender)

Documentation
-----

Expand Down
32 changes: 24 additions & 8 deletions lib/redis_rate_limiter.rb
Expand Up @@ -31,11 +31,11 @@ def initialize key, redis, options = {}
# @param [String] subject A name to uniquely identify subject
# @param [time] time UNIX timestamp of event
def add subject, time = Time.now.to_f
subject = "#{@key}:#{subject}"
subject_key = "#{@key}:#{subject}"
@redis.multi do
@redis.lpush(subject, time)
@redis.ltrim(subject, 0, @limit - 1)
@redis.expire(subject, @interval)
@redis.lpush(subject_key, time)
@redis.ltrim(subject_key, 0, @limit - 1)
@redis.expire(subject_key, @interval)
end
end

Expand All @@ -44,9 +44,25 @@ def add subject, time = Time.now.to_f
# @param [String] subject Name which uniquely identifies subject
# @return [Boolean] Returns true if subject has exceeded count
def exceeded? subject
subject = "#{@key}:#{subject}"
return false if @redis.llen(subject) < @limit
last = @redis.lindex(subject, -1)
Time.now.to_f - last.to_f < @interval
subject_key = "#{@key}:#{subject}"
return false if @redis.llen(subject_key) < @limit
time_since_oldest(subject_key) < @interval
end

# Get time in seconds until subject is not rate limited
#
# @param [String] subject Name which uniquely identifies subject
# @return [Float] Returns time in seconds until subject is not rate limited
def retry_in? subject
subject_key = "#{@key}:#{subject}"
return 0.0 if @redis.llen(subject_key) < @limit
elapsed = time_since_oldest(subject_key)
elapsed > @interval ? 0.0 : @interval - elapsed
end

private

def time_since_oldest subject_key
Time.now.to_f - @redis.lindex(subject_key, -1).to_f
end
end
45 changes: 43 additions & 2 deletions spec/redis_rate_limiter_spec.rb
Expand Up @@ -43,18 +43,59 @@
expect(@rl.exceeded?(subject)).to be_falsey
end
context "subject's list is at limit" do
it "should be false if last item in subject's list is outside interval" do
it "should be false if oldest item in subject's list is outside interval" do
outside_interval_timestamp = Time.now.to_f - @rl.interval - 1
@rl.limit = 5
@rl.add(subject, outside_interval_timestamp)
4.times { @rl.add(subject) }
expect(@rl.exceeded?(subject)).to be_falsey
end
it "should be true if last item in subject's list is within interval" do
it "should be true if oldest item in subject's list is within interval" do
@rl.limit = 5
5.times { @rl.add(subject) }
expect(@rl.exceeded?(subject)).to be_truthy
end
end
end

describe "#retry_in?" do
let(:subject) { "subject" }
before do
allow(Redis).to receive(:new).and_return(MockRedis.new)
@rl = RedisRateLimiter.new(:key, Redis.new)
end
it "should be 0.0 if subject's list is less than limit" do
expect(@rl.retry_in?(subject)).to eq(0.0)
end
context "subject's list is at limit" do
it "should be 0.0 if oldest item in subject's list is outside interval" do
outside_interval_timestamp = Time.now.to_f - @rl.interval - 1
@rl.limit = 5
@rl.add(subject, outside_interval_timestamp)
4.times { @rl.add(subject) }
expect(@rl.retry_in?(subject)).to eq(0.0)
end
it "should be the time in seconds until subject stops being rate limited" do
forty_seconds_ago = Time.now.to_f - 40
@rl.limit = 5
@rl.add(subject, forty_seconds_ago)
4.times { @rl.add(subject) }
expect(@rl.retry_in?(subject)).to be_within(1).of(20)
end
end
end

describe "#time_since_oldest" do
let(:subject) { "subject" }
let(:subject_key) { "key:subject" }
before do
allow(Redis).to receive(:new).and_return(@redis = MockRedis.new)
@rl = RedisRateLimiter.new(:key, Redis.new)
end
it "should return time since oldest event for given subject key" do
forty_seconds_ago = Time.now.to_f - 40
@rl.add(subject, forty_seconds_ago)
expect(@rl.send(:time_since_oldest, subject_key)).to be_within(1).of(40)
end
end
end

0 comments on commit 96d473b

Please sign in to comment.