prop 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -38,7 +38,7 @@ If this method gets called more than "threshold" times within "interval in secon
38
38
 
39
39
  You can chose to override the threshold for a given key:
40
40
 
41
- Prop.mails_per_hour(mail.from, :threshold => account.mail_throttle_threshold)
41
+ Prop.throttle_mails_per_hour!(mail.from, :threshold => account.mail_throttle_threshold)
42
42
 
43
43
  If you wish to reset a specific throttle, you can do that like so:
44
44
 
@@ -49,7 +49,9 @@ When the threshold are invoked without argument, the key is nil and as such a sc
49
49
  Lastly you can use Prop without registering the thresholds up front:
50
50
 
51
51
  Prop.throttle!(:key => 'nuisance@example.com', :threshold => 100, :interval -> 1.hour)
52
+ Prop.throttle?(:key => 'nuisance@example.com', :threshold => 100, :interval -> 1.hour)
52
53
  Prop.reset(:key => 'nuisance@example.com', :threshold => 100, :interval -> 1.hour)
54
+ Prop.count(:key => 'nuisance@example.com', :threshold => 100, :interval -> 1.hour)
53
55
 
54
56
  It's up to you to pass an appropriate key which reflects the scope you're rate limiting. The interval is tied to the underlying key generating mechanism, so if you change that between calls and have all other things equal, then that will result in different throttles being set.
55
57
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.1
1
+ 0.3.2
@@ -39,18 +39,16 @@ class Prop
39
39
  end
40
40
 
41
41
  def throttle?(options)
42
- cache_key = sanitized_prop_key(options)
43
- reader.call(cache_key).to_i >= options[:threshold]
42
+ count(options) >= options[:threshold]
44
43
  end
45
44
 
46
45
  def throttle!(options)
47
- cache_key = sanitized_prop_key(options)
48
- counter = reader.call(cache_key).to_i
46
+ counter = count(options)
49
47
 
50
48
  if counter >= options[:threshold]
51
49
  raise Prop::RateLimitExceededError.new("#{options[:key]} threshold #{options[:threshold]} exceeded")
52
50
  else
53
- writer.call(cache_key, counter + 1)
51
+ writer.call(sanitized_prop_key(options), counter + 1)
54
52
  end
55
53
  end
56
54
 
@@ -66,18 +64,30 @@ class Prop
66
64
 
67
65
  private
68
66
 
67
+ # Builds the expiring cache key
69
68
  def sanitized_prop_key(options)
70
- cache_key = "#{options[:key]}/#{Time.now.to_i / options[:interval]}"
69
+ cache_key = "#{normalize_cache_key(options[:key])}/#{Time.now.to_i / options[:interval]}"
71
70
  "prop/#{Digest::MD5.hexdigest(cache_key)}"
72
71
  end
73
-
72
+
73
+ # Sanitizes the option set and sets defaults
74
74
  def sanitized_prop_options(handle, args, defaults)
75
75
  key = handle.to_s
76
- key << "/#{args.first}" if args.first
76
+ key << "/#{normalize_cache_key(args.first)}" if args.first
77
77
 
78
78
  options = { :key => key, :threshold => defaults[:threshold].to_i, :interval => defaults[:interval].to_i }
79
79
  options = options.merge(args.last) if args.last.is_a?(Hash)
80
80
  options
81
81
  end
82
+
83
+ # Simple key expansion only supports arrays and primitives
84
+ def normalize_cache_key(key)
85
+ if key.is_a?(Array)
86
+ key.map { |part| normalize_cache_key(part) }.join('/')
87
+ else
88
+ key.to_s
89
+ end
90
+ end
91
+
82
92
  end
83
93
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{prop}
8
- s.version = "0.3.1"
8
+ s.version = "0.3.2"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Morten Primdahl"]
12
- s.date = %q{2010-07-21}
12
+ s.date = %q{2010-07-22}
13
13
  s.description = %q{A gem for implementing rate limiting}
14
14
  s.email = %q{morten@zendesk.com}
15
15
  s.extra_rdoc_files = [
@@ -26,6 +26,8 @@ class TestProp < Test::Unit::TestCase
26
26
  should "accept a handle and an options hash" do
27
27
  Prop.setup :hello_there, :threshold => 40, :interval => 100
28
28
  assert Prop.respond_to?(:throttle_hello_there!)
29
+ assert Prop.respond_to?(:throttle_hello_there?)
30
+ assert Prop.respond_to?(:reset_hello_there)
29
31
  end
30
32
 
31
33
  should "result in a default handle" do
@@ -38,9 +40,14 @@ class TestProp < Test::Unit::TestCase
38
40
  assert_equal 5, Prop.throttle_hello_there!('some key', :threshold => 20)
39
41
  end
40
42
 
41
- should "create a handle accepts integer keys" do
43
+ should "create a handle accepts various cache key types" do
42
44
  Prop.setup :hello_there, :threshold => 4, :interval => 10
43
- assert Prop.throttle_hello_there!(5)
45
+ assert_equal 1, Prop.throttle_hello_there!(5)
46
+ assert_equal 2, Prop.throttle_hello_there!(5)
47
+ assert_equal 1, Prop.throttle_hello_there!('mister')
48
+ assert_equal 2, Prop.throttle_hello_there!('mister')
49
+ assert_equal 1, Prop.throttle_hello_there!(['mister', 5])
50
+ assert_equal 2, Prop.throttle_hello_there!(['mister', 5])
44
51
  end
45
52
 
46
53
  should "not shadow undefined methods" do
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prop
3
3
  version: !ruby/object:Gem::Version
4
- hash: 17
4
+ hash: 23
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 3
9
- - 1
10
- version: 0.3.1
9
+ - 2
10
+ version: 0.3.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Morten Primdahl
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-07-21 00:00:00 -07:00
18
+ date: 2010-07-22 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency