prop 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -15,9 +15,11 @@ To get going with Prop you first define the read and write operations. These def
15
15
 
16
16
  You can choose to rely on a database or Moneta or Redis or whatever you'd like to use for transient storage. Prop does not do any sort of clean up of its key space, so you would have to implement that manually should you be using anything but an LRU cache.
17
17
 
18
+ == Defining thresholds
19
+
18
20
  Once the read and write operations are defined, you can optionally define thresholds. If for example, you want to have a threshold on accepted emails per hour from a given user, you could define a threshold and interval (in seconds) for this like so:
19
21
 
20
- Prop.configure(:mails_per_hour, :threshold => 100, :interval => 1.hour)
22
+ Prop.configure(:mails_per_hour, :threshold => 100, :interval => 1.hour, :description => "Mail rate limit exceeded")
21
23
 
22
24
  You can now put the throttle to work with this values, by passing the "handle" to the respective methods in Prop:
23
25
 
@@ -33,6 +35,10 @@ You can now put the throttle to work with this values, by passing the "handle" t
33
35
  # Returns the value of this throttle, usually a count, but see below for more
34
36
  Prop.query(:mails_per_hour)
35
37
 
38
+ Prop will raise a RuntimeError if you attempt to operate on an undefined handle.
39
+
40
+ == Scoping a throttle
41
+
36
42
  In many cases you will want to tie a specific key to a defined throttle, for example you can scope the throttling to a specific sender rather than running a global "mails per hour" throttle:
37
43
 
38
44
  Prop.throttle!(:mails_per_hour, mail.from)
@@ -44,18 +50,31 @@ The throttle scope can also be an array of values, e.g.:
44
50
 
45
51
  Prop.throttle!(:mails_per_hour, [ account.id, mail.from ])
46
52
 
47
- If the throttle! method gets called more than "threshold" times within "interval in seconds" for a given handle and key combination, Prop throws a Prop::RateLimitExceededError. This exception contains a "handle" reference, which is handy when you are using Prop in multiple locations and want to be able to differentiate further up the stack. For example, in Rails you can use this in e.g. ApplicationController:
53
+ == Error handling
48
54
 
49
- THROTTLE_MESSAGES = Hash.new("Throttle exceeded")
50
- THROTTLE_MESSAGES[:login] = "Too many invalid login attempts. Try again later."
55
+ If the throttle! method gets called more than "threshold" times within "interval in seconds" for a given handle and key combination, Prop throws a Prop::RateLimitExceededError. This exception contains a "handle" reference and a "description" if specified during the configuration. The handle allows you to rescue Prop::RateLimitExceededError and differentiate action depending on the handle. For example, in Rails you can use this in e.g. ApplicationController:
51
56
 
52
- rescue_from Prop::RateLimitExceededError do |exception|
53
- render :status => 403, :message => I18n.t(THROTTLE_MESSAGES[exception.handle])
57
+ rescue_from Prop::RateLimitExceededError do |e|
58
+ if e.handle == :authorization_attempt
59
+ render :status => :forbidden, :message => I18n.t(e.description)
60
+ elsif ...
61
+
62
+ end
54
63
  end
55
64
 
65
+ == Disabling Prop
66
+
67
+ In case you need to perform e.g. a manual bulk operation:
68
+
69
+ Prop.disabled do
70
+ # No throttles will be tested here
71
+ end
72
+
73
+ == Threshold settings
74
+
56
75
  You can chose to override the threshold for a given key:
57
76
 
58
- Prop.throttle!(:mails_per_hour, mail.from, :threshold => account.mail_throttle_threshold)
77
+ Prop.throttle!(:mails_per_hour, mail.from, :threshold => current_account.mail_throttle_threshold)
59
78
 
60
79
  When the threshold are invoked without argument, the key is nil and as such a scope of its own, i.e. these are equivalent:
61
80
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.1
1
+ 0.6.2
@@ -8,12 +8,14 @@ end
8
8
 
9
9
  class Prop
10
10
  class RateLimitExceededError < RuntimeError
11
- attr_accessor :handle, :retry_after
11
+ attr_accessor :handle, :retry_after, :description
12
12
 
13
- def self.create(handle, key, threshold)
13
+ def self.create(handle, key, threshold, description = nil)
14
14
  error = new("#{handle} threshold of #{threshold} exceeded for key '#{key}'")
15
+ error.description = description
15
16
  error.handle = handle
16
17
  error.retry_after = threshold - Time.now.to_i % threshold if threshold > 0
18
+
17
19
  raise error
18
20
  end
19
21
  end
@@ -56,7 +58,7 @@ class Prop
56
58
  return counter if disabled?
57
59
 
58
60
  if counter >= options[:threshold]
59
- raise Prop::RateLimitExceededError.create(handle, normalize_cache_key(key), options[:threshold])
61
+ raise Prop::RateLimitExceededError.create(handle, normalize_cache_key(key), options[:threshold], options[:description])
60
62
  else
61
63
  writer.call(cache_key, counter + [ 1, options[:increment].to_i ].max)
62
64
  end
@@ -94,7 +96,9 @@ class Prop
94
96
 
95
97
  # Sanitizes the option set and sets defaults
96
98
  def sanitized_prop_options(handle, key, options)
97
- defaults = (handles || {})[handle] || {}
99
+ raise RuntimeError.new("No such handle configured: #{handle.inspect}") if handles.nil? || handles[handle].nil?
100
+
101
+ defaults = handles[handle]
98
102
  return {
99
103
  :key => normalize_cache_key(key),
100
104
  :increment => defaults[:increment],
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{prop}
8
- s.version = "0.6.1"
8
+ s.version = "0.6.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{2011-08-18}
12
+ s.date = %q{2011-08-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 = [
@@ -144,14 +144,21 @@ class TestProp < Test::Unit::TestCase
144
144
  end
145
145
 
146
146
  begin
147
- Prop.throttle!(:hello, nil, :threshold => 5, :interval => 10)
147
+ Prop.throttle!(:hello, nil, :threshold => 5, :interval => 10, :description => "Boom!")
148
148
  fail
149
149
  rescue Prop::RateLimitExceededError => e
150
150
  assert_equal :hello, e.handle
151
151
  assert_equal "hello threshold of 5 exceeded for key ''", e.message
152
+ assert_equal "Boom!", e.description
152
153
  assert e.retry_after
153
154
  end
154
155
  end
156
+
157
+ should "raise a RuntimeError when a handle has not been configured" do
158
+ assert_raises(RuntimeError) do
159
+ Prop.throttle!(:no_such_handle, nil, :threshold => 5, :interval => 10)
160
+ end
161
+ end
155
162
  end
156
163
 
157
164
  end
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: 5
4
+ hash: 3
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 6
9
- - 1
10
- version: 0.6.1
9
+ - 2
10
+ version: 0.6.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: 2011-08-18 00:00:00 -07:00
18
+ date: 2011-08-22 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency