throttling 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,9 @@
1
+ ## 0.3.0 (April 13, 2012)
2
+
3
+ Features:
4
+
5
+ - Added ability to retrieve custom value based on number of actions performed in a period of time
6
+
1
7
  ## 0.2.0 (April 12, 2012)
2
8
 
3
9
  Bugfixes:
data/README.md CHANGED
@@ -46,6 +46,34 @@ The basic structure of the file is:
46
46
  limit: 10000
47
47
  period: 86400
48
48
 
49
+ request_priority:
50
+ period: 86400
51
+ default_value: 25
52
+ values:
53
+ high_priority:
54
+ limit: 5
55
+ value: 10
56
+ medium_priority:
57
+ limit: 15
58
+ value: 15
59
+ low_priority:
60
+ limit: 100
61
+ value: 20
62
+
63
+ This example covers three different scenarios:
64
+
65
+ 1. Single period. In this case only 20 actions will be allowed in a period of
66
+ one hour (3600 seconds).
67
+
68
+ 2. Multiple periods. Action will be allowed to perform 300 times in 10 minutes,
69
+ 1000 times an hour, and 10000 times a day.
70
+
71
+ 3. This special case covers following scenario: based on the number of actions,
72
+ it returns a value, or default value when largest limit is reached. In this
73
+ case it will return 10, when there were 5 or less requests (including current one),
74
+ 15 for up to 15 requests, 20 for up to 100 requests, and 25 when there were
75
+ more than 100 requests.
76
+
49
77
  You can also specify limits as a Hash:
50
78
 
51
79
  Throttling.limits = {
@@ -100,6 +128,59 @@ You can add more helpers like this:
100
128
  end
101
129
  end
102
130
 
131
+ ## Use cases
132
+
133
+ ### Limiting number of sign-ups
134
+
135
+ user_signup:
136
+ limit: 20
137
+ period: 3600
138
+
139
+ Limit the number of sign-ups to 20 per hour per IP address:
140
+
141
+ Throttling.for('user_signup').check_ip(request.remote_ip)
142
+
143
+ ### Limiting number of document uploads
144
+
145
+ document_uploads:
146
+ minutely:
147
+ limit: 5
148
+ period: 600
149
+ hourly:
150
+ limit: 10
151
+ period: 3600
152
+ daily:
153
+ limit: 50
154
+ period: 86400
155
+
156
+ In this case user will be allowed to upload 5 documents in 10 minutes, 10 documents
157
+ in an hour, or 50 documents a day:
158
+
159
+ Throttling.for('document_uploads').check_user_id(current_user.id)
160
+
161
+ ### Prioritizing uploads based on number of uploads
162
+
163
+ document_priorities:
164
+ period: 86400
165
+ default_value: 25
166
+ values:
167
+ high_priority:
168
+ limit: 5
169
+ value: 10
170
+ medium_priority:
171
+ limit: 15
172
+ value: 15
173
+ low_priority:
174
+ limit: 100
175
+ value: 20
176
+
177
+ All documents could be prioritized based on the number of uploads: if user uploads
178
+ less than 5 documents a day, they all will have priority 10. Next 10 documents
179
+ (first five keep their original priority) will receive priority 15. Documents
180
+ 16 to 100 will get priority 20, and everything else will get priority 25.
181
+
182
+ Throttling.for('document_priorities').check_user_id(current_user.id)
183
+
103
184
  ## Contributing
104
185
 
105
186
  1. Fork it
@@ -11,7 +11,10 @@ module Throttling
11
11
  raise ArgumentError, "No Throttling.limits[#{action}] section found" unless limits
12
12
 
13
13
  # Convert simple limits to a hash
14
- if @limits[:limit] && @limits[:period]
14
+ if @limits[:period]
15
+ if @limits[:values]
16
+ @limits[:values] = @limits[:values].sort_by { |name, params| params && params[:limit] }
17
+ end
15
18
  @limits = [[ 'global', @limits ]]
16
19
  else
17
20
  @limits = @limits.sort_by { |name, params| params && params[:period] }
@@ -31,19 +34,33 @@ module Throttling
31
34
  return true if !Throttling.enabled? || check_value.nil?
32
35
 
33
36
  limits.each do |period_name, params|
34
- raise ArgumentError, "Invalid or no 'period' parameter in the limits[#{period_name}] config" if params[:period].to_i < 1
35
- raise ArgumentError, "Invalid or no 'limit' parameter in the limits[#{period_name}] config" if params[:limit].nil? || params[:limit].to_i < 0
36
-
37
37
  period = params[:period].to_i
38
+ limit = params[:limit].to_i
39
+ values = params[:values]
40
+
41
+ raise ArgumentError, "Invalid or no 'period' parameter in the limits[#{period_name}] config" if period < 1
42
+ raise ArgumentError, "Invalid or no 'limit' parameter in the limits[#{period_name}] config" if limit < 1 && !values
43
+
38
44
  key = hits_store_key(check_type, check_value, period_name, period)
39
45
 
40
46
  # Retrieve current value
41
- hits = Throttling.storage.fetch(key, :expires_in => hits_store_ttl(period), :raw => true) { '0' }
47
+ hits = Throttling.storage.fetch(key, :expires_in => hits_store_ttl(period), :raw => true) { '0' }.to_i
42
48
 
43
- # Over limit?
44
- return false if hits.to_i > params[:limit].to_i
49
+ if values
50
+ value = params[:default_value] || false
51
+ values.each do |value_name, value_params|
52
+ if hits < value_params[:limit].to_i
53
+ value = value_params[:value] || value_params[:default_value] || false
54
+ break
55
+ end
56
+ end
57
+ else
58
+ # Over limit?
59
+ return false if hits > limit
60
+ end
45
61
 
46
62
  Throttling.storage.increment(key) if auto_increment
63
+ return value if values
47
64
  end
48
65
 
49
66
  return true
@@ -1,3 +1,3 @@
1
1
  module Throttling
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -97,6 +97,50 @@ describe Throttling do
97
97
  end
98
98
  end
99
99
 
100
+ context 'with values specified' do
101
+ before do
102
+ Throttling.limits_config = File.expand_path('../fixtures/throttling.yml', __FILE__)
103
+ end
104
+
105
+ it 'should return value when limit is not reached' do
106
+ @storage.should_receive(:fetch).and_return(0)
107
+ Throttling.for('request_priority').check_ip('127.0.0.1').should == 10
108
+ @storage.should_receive(:fetch).and_return(4)
109
+ Throttling.for('request_priority').check_ip('127.0.0.1').should == 10
110
+
111
+ @storage.should_receive(:fetch).and_return(5)
112
+ Throttling.for('request_priority').check_ip('127.0.0.1').should == 15
113
+ @storage.should_receive(:fetch).and_return(14)
114
+ Throttling.for('request_priority').check_ip('127.0.0.1').should == 15
115
+
116
+ @storage.should_receive(:fetch).and_return(15)
117
+ Throttling.for('request_priority').check_ip('127.0.0.1').should == 20
118
+ @storage.should_receive(:fetch).and_return(99)
119
+ Throttling.for('request_priority').check_ip('127.0.0.1').should == 20
120
+
121
+ @storage.should_receive(:fetch).and_return(100)
122
+ Throttling.for('request_priority').check_ip('127.0.0.1').should == 25
123
+ @storage.should_receive(:fetch).and_return(1000)
124
+ Throttling.for('request_priority').check_ip('127.0.0.1').should == 25
125
+ end
126
+
127
+ it 'should increase hit counter' do
128
+ @storage.should_receive(:fetch).and_return(4)
129
+ @storage.should_receive(:increment)
130
+ Throttling.for('request_priority').check_ip('127.0.0.1')
131
+
132
+ @storage.should_receive(:fetch).and_return(1000)
133
+ @storage.should_receive(:increment)
134
+ Throttling.for('request_priority').check_ip('127.0.0.1')
135
+ end
136
+
137
+ it 'should return false when highest limit reached' do
138
+ Throttling.limits['request_priority'].delete('default_value')
139
+ @storage.should_receive(:fetch).and_return(1000)
140
+ Throttling.for('request_priority').check_ip('127.0.0.1').should be_false
141
+ end
142
+ end
143
+
100
144
  context do
101
145
  before do
102
146
  Throttling.limits = { 'foo' => {'limit' => 5, 'period' => 86400} }
@@ -12,3 +12,17 @@ search_requests:
12
12
  daily:
13
13
  limit: 10000
14
14
  period: 86400
15
+
16
+ request_priority:
17
+ period: 86400
18
+ default_value: 25
19
+ values:
20
+ high_priority:
21
+ limit: 5
22
+ value: 10
23
+ medium_priority:
24
+ limit: 15
25
+ value: 15
26
+ low_priority:
27
+ limit: 100
28
+ value: 20
@@ -15,9 +15,9 @@ Gem::Specification.new do |gem|
15
15
  gem.add_development_dependency 'rb-fsevent'
16
16
  gem.add_development_dependency 'growl'
17
17
 
18
- gem.files = `git ls-files`.split($\)
18
+ gem.files = `git ls-files`.split($\) - %w[.travis.yml]
19
19
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
20
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) + %w[Guardfile]
21
21
  gem.name = "throttling"
22
22
  gem.require_paths = ["lib"]
23
23
  gem.version = Throttling::VERSION
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: throttling
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-04-12 00:00:00.000000000 Z
13
+ date: 2012-04-13 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rake
17
- requirement: &70166425496360 !ruby/object:Gem::Requirement
17
+ requirement: &70192665062020 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>='
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: '0'
23
23
  type: :development
24
24
  prerelease: false
25
- version_requirements: *70166425496360
25
+ version_requirements: *70192665062020
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rspec
28
- requirement: &70166425495940 !ruby/object:Gem::Requirement
28
+ requirement: &70192665061600 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ! '>='
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
- version_requirements: *70166425495940
36
+ version_requirements: *70192665061600
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: timecop
39
- requirement: &70166425495520 !ruby/object:Gem::Requirement
39
+ requirement: &70192665061180 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ! '>='
@@ -44,10 +44,10 @@ dependencies:
44
44
  version: '0'
45
45
  type: :development
46
46
  prerelease: false
47
- version_requirements: *70166425495520
47
+ version_requirements: *70192665061180
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: guard-rspec
50
- requirement: &70166425495100 !ruby/object:Gem::Requirement
50
+ requirement: &70192665060760 !ruby/object:Gem::Requirement
51
51
  none: false
52
52
  requirements:
53
53
  - - ! '>='
@@ -55,10 +55,10 @@ dependencies:
55
55
  version: '0'
56
56
  type: :development
57
57
  prerelease: false
58
- version_requirements: *70166425495100
58
+ version_requirements: *70192665060760
59
59
  - !ruby/object:Gem::Dependency
60
60
  name: rb-fsevent
61
- requirement: &70166425494680 !ruby/object:Gem::Requirement
61
+ requirement: &70192665060340 !ruby/object:Gem::Requirement
62
62
  none: false
63
63
  requirements:
64
64
  - - ! '>='
@@ -66,10 +66,10 @@ dependencies:
66
66
  version: '0'
67
67
  type: :development
68
68
  prerelease: false
69
- version_requirements: *70166425494680
69
+ version_requirements: *70192665060340
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: growl
72
- requirement: &70166425494260 !ruby/object:Gem::Requirement
72
+ requirement: &70192665059920 !ruby/object:Gem::Requirement
73
73
  none: false
74
74
  requirements:
75
75
  - - ! '>='
@@ -77,7 +77,7 @@ dependencies:
77
77
  version: '0'
78
78
  type: :development
79
79
  prerelease: false
80
- version_requirements: *70166425494260
80
+ version_requirements: *70192665059920
81
81
  description: Throttling gem provides basic, but very powerful way to throttle various
82
82
  user actions in your application
83
83
  email:
@@ -87,7 +87,6 @@ extensions: []
87
87
  extra_rdoc_files: []
88
88
  files:
89
89
  - .gitignore
90
- - .travis.yml
91
90
  - CHANGELOG.md
92
91
  - Gemfile
93
92
  - Guardfile
@@ -132,3 +131,4 @@ test_files:
132
131
  - spec/fixtures/throttling.yml
133
132
  - spec/spec_helper.rb
134
133
  - spec/throttling_spec.rb
134
+ - Guardfile
@@ -1,9 +0,0 @@
1
- rvm:
2
- - 1.8.7
3
- - 1.9.3
4
- - ree
5
- - jruby
6
-
7
- notifications:
8
- recipients:
9
- - kpumuk@kpumuk.info