curbit 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ .document
@@ -65,6 +65,24 @@ instance as an argument.
65
65
  because the Proc is not bound to the controller instance when it's
66
66
  defined. This way, you can at least have access to stuff you might need.)
67
67
 
68
+ === Conditional rate limiting
69
+
70
+ You may want to apply rate limiting only in certain conditions, for example, if
71
+ a user is authenticated. Curbit provides the :if and :unless parameters that
72
+ both accept a symbol or Proc. If a symbol is passed then Curbit attempts
73
+ to execute a method using the symbol as the method name. In the case of the :if parameter,
74
+ the rate limit is applied if the method returns true. For the :unless paramter, the rate
75
+ limit is not applied if the method returns true.
76
+
77
+ rate_limit :invite,
78
+ :max_calls => 2,
79
+ :time_limit => 30.seconds,
80
+ :wait_time => 1.minute,
81
+ :unless => :logged_in?
82
+
83
+ In this case, the rate limiting is being used with the acts_as_authenticated method logged_in?
84
+ so that the rate limiting is only applied if the user is not logged in.
85
+
68
86
  === Custom message
69
87
 
70
88
  You might like to customize the messages returned by CurbIt.
data/Rakefile CHANGED
@@ -53,3 +53,4 @@ Rake::RDocTask.new do |rdoc|
53
53
  rdoc.rdoc_files.include('README*')
54
54
  rdoc.rdoc_files.include('lib/**/*.rb')
55
55
  end
56
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -1,31 +1,62 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
1
4
  # -*- encoding: utf-8 -*-
2
5
 
3
6
  Gem::Specification.new do |s|
4
7
  s.name = %q{curbit}
5
- s.version = "0.1.2"
8
+ s.version = "0.2.0"
6
9
 
7
- s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
11
  s.authors = ["Scott Sayles"]
9
- s.date = %q{2009-10-25}
10
- s.description = %q{Application level rate limiting for Rails}
12
+ s.date = %q{2010-01-26}
13
+ s.description = %q{Rate limiting for Rails action controllers.}
11
14
  s.email = %q{ssayles@users.sourceforge.net}
12
- s.extra_rdoc_files = ["LICENSE", "README.rdoc", "lib/curbit.rb"]
13
- s.files = ["LICENSE", "README.rdoc", "Rakefile", "init.rb", "lib/curbit.rb", "test/custom_key_controller_test.rb", "test/custom_message_format_controller.rb", "test/standard_controller_test.rb", "test/test_helper.rb", "test/test_rails_helper.rb", "Manifest", "curbit.gemspec"]
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "LICENSE",
22
+ "Manifest",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "curbit.gemspec",
27
+ "init.rb",
28
+ "lib/curbit.rb",
29
+ "test/conditional_controller_test.rb",
30
+ "test/custom_key_controller_test.rb",
31
+ "test/custom_message_format_controller.rb",
32
+ "test/standard_controller_test.rb",
33
+ "test/test_helper.rb",
34
+ "test/test_rails_helper.rb"
35
+ ]
14
36
  s.homepage = %q{http://github.com/ssayles/curbit}
15
- s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Curbit", "--main", "README.rdoc"]
37
+ s.rdoc_options = ["--charset=UTF-8"]
16
38
  s.require_paths = ["lib"]
17
- s.rubyforge_project = %q{curbit}
18
39
  s.rubygems_version = %q{1.3.5}
19
- s.summary = %q{Application level rate limiting for Rails}
20
- s.test_files = ["test/custom_key_controller_test.rb", "test/standard_controller_test.rb", "test/test_helper.rb", "test/test_rails_helper.rb"]
40
+ s.summary = %q{Rate limiting for Rails action controllers.}
41
+ s.test_files = [
42
+ "test/conditional_controller_test.rb",
43
+ "test/custom_key_controller_test.rb",
44
+ "test/custom_message_format_controller.rb",
45
+ "test/standard_controller_test.rb",
46
+ "test/test_helper.rb",
47
+ "test/test_rails_helper.rb"
48
+ ]
21
49
 
22
50
  if s.respond_to? :specification_version then
23
51
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
52
  s.specification_version = 3
25
53
 
26
54
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
55
+ s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
27
56
  else
57
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
28
58
  end
29
59
  else
60
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
30
61
  end
31
62
  end
@@ -20,6 +20,8 @@ module Curbit
20
20
  # * +wait_time+ - The time to wait if :max_calls has been reached before being able to pass.
21
21
  # * +message+ - The message to render to the client if the call is being limited. The message will be rendered as a correspondingly formatted response with a default status if given a String. If the argument is a symbol, a method with the same name will be invoked with the specified wait_time (in seconds). The called method should take care of rendering the response.
22
22
  # * +status+ - The response status to set when the call is being limited.
23
+ # * +if+ - A symbol representing a method or Proc that returns true if the rate limiting should be applied.
24
+ # * +unless+ - A symbol representing a method or Proc that returns true if the rate limiting should NOT be applied.
23
25
  #
24
26
  # ==== Examples
25
27
  #
@@ -64,6 +66,9 @@ module Curbit
64
66
  def rate_limit_opts_valid?(opts = {})
65
67
  new_opts = {:status => 503}.merge! opts
66
68
  opts.merge! new_opts
69
+ if opts.key?(:if) and opts.key?(:unless)
70
+ raise ":unless and :if are mutually exclusive parameters"
71
+ end
67
72
  if !opts.key?(:max_calls) or !opts.key?(:time_limit) or !opts.key?(:wait_time)
68
73
  raise ":max_calls, :time_limit, and :wait_time are required parameters"
69
74
  end
@@ -80,7 +85,28 @@ module Curbit
80
85
  "#{CacheKeyPrefix}_#{self.class.name}_#{method}_#{key}"
81
86
  end
82
87
 
88
+ def rate_limit_conditional(opts)
89
+ if opts.key?(:unless)
90
+ if opts[:unless].is_a? Proc
91
+ return true if opts[:unless].call(self)
92
+ elsif opts[:unless].is_a? Symbol
93
+ return true if self.send(opts[:unless])
94
+ end
95
+ end
96
+ if opts.key?(:if)
97
+ if opts[:if].is_a? Proc
98
+ return true unless opts[:if].call(self)
99
+ elsif opts[:if].is_a? Symbol
100
+ return true unless self.send(opts[:if])
101
+ end
102
+ end
103
+ return false
104
+ end
105
+
83
106
  def rate_limit_filter(method, opts)
107
+
108
+ return true if rate_limit_conditional(opts)
109
+
84
110
  key = get_key(opts[:key])
85
111
  unless (key)
86
112
  return true
@@ -97,6 +123,8 @@ module Curbit
97
123
  val[:count] = count + 1
98
124
  started_waiting = val[:started_waiting]
99
125
 
126
+ # did we start making the user wait before being allowed to make
127
+ # another call?
100
128
  if started_waiting
101
129
  # did we exceed the wait time?
102
130
  if Time.now.to_i > (started_waiting.to_i + opts[:wait_time])
@@ -0,0 +1,67 @@
1
+ require 'test_helper'
2
+ require 'test_rails_helper'
3
+
4
+ class ConditionalController < ActionController::Base
5
+
6
+ include Curbit::Controller
7
+
8
+ attr_accessor :rendered
9
+
10
+ def index
11
+ render :text => 'index action'
12
+ end
13
+
14
+ def show
15
+ render :text => 'show action'
16
+ end
17
+
18
+ rate_limit :index, :max_calls => 2,
19
+ :time_limit => 30.seconds,
20
+ :wait_time => 2.minute,
21
+ :if => :logged_in?
22
+
23
+ rate_limit :show, :max_calls => 2,
24
+ :time_limit => 30.seconds,
25
+ :wait_time => 2.minute,
26
+ :unless => :logged_in?
27
+
28
+
29
+ protected
30
+
31
+ def logged_in?
32
+ true
33
+ end
34
+
35
+ end
36
+
37
+
38
+ class ConditionalControllerTest < ActionController::TestCase
39
+ tests ConditionalController
40
+
41
+ context "When calling a rate limited method with " do
42
+ setup {
43
+ @env = {'HTTP_X_FORWARDED_FOR' => '192.168.1.123'}
44
+ @request.stubs(:env).returns(@env)
45
+ cache_value = {:started => Time.now.to_i - 15.seconds,
46
+ :count => 2
47
+ }
48
+ Rails.cache.stubs(:read).returns(cache_value)
49
+ Rails.cache.stubs(:write)
50
+ }
51
+ context ":if, it" do
52
+ should "should call a method named by the symbol" do
53
+ get :index
54
+ assert_equal "503 Service Unavailable", @response.status
55
+ end
56
+ end
57
+ context ":unless, it" do
58
+ should "should call a method named by the symbol" do
59
+ get :show
60
+ assert_equal "show action", @response.body
61
+ end
62
+ end
63
+
64
+
65
+ end
66
+ end
67
+
@@ -86,11 +86,16 @@ class CurbiControllerTest < ActionController::TestCase
86
86
  }
87
87
  context "and max calls has been exceeded for the current time limit" do
88
88
  setup {
89
+ # emulate the state needed to indicate that we've started
90
+ # waiting on calls
89
91
  cache_value = {:started => Time.now.to_i - 15.seconds,
90
92
  :count => 1
91
93
  }
92
94
  Rails.cache.stubs(:read).returns(cache_value)
93
- Rails.cache.stubs(:write)
95
+ Rails.cache.stubs(:write).with() {|key, val, duration|
96
+ cache_value[:count] = val[:count]
97
+ true;
98
+ }
94
99
  }
95
100
  context ", the call" do
96
101
  should "be blocked" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: curbit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Sayles
@@ -9,11 +9,20 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-25 00:00:00 -04:00
12
+ date: 2010-01-26 00:00:00 -05:00
13
13
  default_executable:
14
- dependencies: []
15
-
16
- description: Application level rate limiting for Rails
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: thoughtbot-shoulda
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: Rate limiting for Rails action controllers.
17
26
  email: ssayles@users.sourceforge.net
18
27
  executables: []
19
28
 
@@ -22,32 +31,29 @@ extensions: []
22
31
  extra_rdoc_files:
23
32
  - LICENSE
24
33
  - README.rdoc
25
- - lib/curbit.rb
26
34
  files:
35
+ - .gitignore
27
36
  - LICENSE
37
+ - Manifest
28
38
  - README.rdoc
29
39
  - Rakefile
40
+ - VERSION
41
+ - curbit.gemspec
30
42
  - init.rb
31
43
  - lib/curbit.rb
44
+ - test/conditional_controller_test.rb
32
45
  - test/custom_key_controller_test.rb
33
46
  - test/custom_message_format_controller.rb
34
47
  - test/standard_controller_test.rb
35
48
  - test/test_helper.rb
36
49
  - test/test_rails_helper.rb
37
- - Manifest
38
- - curbit.gemspec
39
50
  has_rdoc: true
40
51
  homepage: http://github.com/ssayles/curbit
41
52
  licenses: []
42
53
 
43
54
  post_install_message:
44
55
  rdoc_options:
45
- - --line-numbers
46
- - --inline-source
47
- - --title
48
- - Curbit
49
- - --main
50
- - README.rdoc
56
+ - --charset=UTF-8
51
57
  require_paths:
52
58
  - lib
53
59
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -60,17 +66,19 @@ required_rubygems_version: !ruby/object:Gem::Requirement
60
66
  requirements:
61
67
  - - ">="
62
68
  - !ruby/object:Gem::Version
63
- version: "1.2"
69
+ version: "0"
64
70
  version:
65
71
  requirements: []
66
72
 
67
- rubyforge_project: curbit
73
+ rubyforge_project:
68
74
  rubygems_version: 1.3.5
69
75
  signing_key:
70
76
  specification_version: 3
71
- summary: Application level rate limiting for Rails
77
+ summary: Rate limiting for Rails action controllers.
72
78
  test_files:
79
+ - test/conditional_controller_test.rb
73
80
  - test/custom_key_controller_test.rb
81
+ - test/custom_message_format_controller.rb
74
82
  - test/standard_controller_test.rb
75
83
  - test/test_helper.rb
76
84
  - test/test_rails_helper.rb