throttling 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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