prop 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +3 -1
- data/VERSION +1 -1
- data/lib/prop.rb +18 -8
- data/prop.gemspec +2 -2
- data/test/test_prop.rb +9 -2
- metadata +4 -4
data/README.rdoc
CHANGED
@@ -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.
|
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
|
+
0.3.2
|
data/lib/prop.rb
CHANGED
@@ -39,18 +39,16 @@ class Prop
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def throttle?(options)
|
42
|
-
|
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
|
-
|
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(
|
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
|
data/prop.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{prop}
|
8
|
-
s.version = "0.3.
|
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-
|
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 = [
|
data/test/test_prop.rb
CHANGED
@@ -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
|
43
|
+
should "create a handle accepts various cache key types" do
|
42
44
|
Prop.setup :hello_there, :threshold => 4, :interval => 10
|
43
|
-
|
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:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 3
|
9
|
-
-
|
10
|
-
version: 0.3.
|
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-
|
18
|
+
date: 2010-07-22 00:00:00 -07:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|