public
Description: Simple plugin which allows you to throttle certain activities in your web apps. Uses memcached for speedy implementation and requires Rails 2.1+
Homepage: http://www.shopify.com
Clone URL: git://github.com/tobi/throttle.git
Tobias Lütke (author)
Thu May 01 15:04:03 -0700 2008
throttle / lib / throttle.rb
100644 62 lines (50 sloc) 1.761 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
require 'digest/md5'
 
# Throttle arbituary operation. Once limit is reached it will raise a
# Throttle::LimitExeeded exception.
#
# Example:
#
# Throttle.for("feed:#{request.remote_ip}", :max => 20, :in => 10.minutes) do
# render :xml => Articles.all
# end
#
# If you want to clear the timeout for the current block ( for example: fraud protection. Clear the throttle when
# the submitted Credit Card was valid. ) your block can accept a yielded throttle object and call the clear method
#
# Throttle.for("cc:#{request.remote_ip}", :max => 20, :in => 10.minutes) do |throttle|
# if am.pay(@credit_card)
# throttle.clear
# redirect_to :action => 'done'
# end
# end
#
class Throttle
  class LimitExceeded < StandardError
  end
  
  def self.for(key, options = {})
    throttle = self.new(key, options)
    if ActionController::Base.perform_caching
      throttle.increment_counter
    end
  
    yield throttle
  end
  
  def initialize(key, options)
    @key = key.blank? ? nil : "throttle:#{Digest::MD5.hexdigest(key.to_s)}"
    @max, @timeout = options[:max].to_i, options[:in].to_i
  end
  
  def increment_counter
    return if @key.blank?
    
    count = Rails.cache.increment(@key)
 
    if count.nil?
      Rails.cache.write @key, 1, :expires_in => @timeout
    elsif @max <= count
      raise LimitExceeded, "Too many requests for operation"
    end
  end
  
  def clear
    return if @key.blank?
    return unless ActionController::Base.perform_caching
    
    Rails.cache.delete @key
    true
  end
  
end