rate_limiter 0.0.1
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.
- data/.gitignore +7 -0
- data/.rspec +3 -0
- data/.rvmrc +80 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +80 -0
- data/README.md +65 -0
- data/Rakefile +6 -0
- data/lib/rate_limiter.rb +24 -0
- data/lib/rate_limiter/config.rb +10 -0
- data/lib/rate_limiter/model.rb +52 -0
- data/lib/rate_limiter/version.rb +3 -0
- data/rate_limiter.gemspec +25 -0
- data/spec/rate_limiter/rate_limiter_spec.rb +10 -0
- data/spec/spec_helper.rb +6 -0
- metadata +110 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# This is an RVM Project .rvmrc file, used to automatically load the ruby
|
4
|
+
# development environment upon cd'ing into the directory
|
5
|
+
|
6
|
+
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
|
7
|
+
environment_id="ruby-1.9.3-p327@rate_limiter"
|
8
|
+
|
9
|
+
#
|
10
|
+
# Uncomment the following lines if you want to verify rvm version per project
|
11
|
+
#
|
12
|
+
# rvmrc_rvm_version="1.10.2" # 1.10.1 seams as a safe start
|
13
|
+
# eval "$(echo ${rvm_version}.${rvmrc_rvm_version} | awk -F. '{print "[[ "$1*65536+$2*256+$3" -ge "$4*65536+$5*256+$6" ]]"}' )" || {
|
14
|
+
# echo "This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading."
|
15
|
+
# return 1
|
16
|
+
# }
|
17
|
+
#
|
18
|
+
|
19
|
+
#
|
20
|
+
# Uncomment following line if you want options to be set only for given project.
|
21
|
+
#
|
22
|
+
# PROJECT_JRUBY_OPTS=( --1.9 )
|
23
|
+
#
|
24
|
+
# The variable PROJECT_JRUBY_OPTS requires the following to be run in shell:
|
25
|
+
#
|
26
|
+
# chmod +x ${rvm_path}/hooks/after_use_jruby_opts
|
27
|
+
#
|
28
|
+
|
29
|
+
#
|
30
|
+
# First we attempt to load the desired environment directly from the environment
|
31
|
+
# file. This is very fast and efficient compared to running through the entire
|
32
|
+
# CLI and selector. If you want feedback on which environment was used then
|
33
|
+
# insert the word 'use' after --create as this triggers verbose mode.
|
34
|
+
#
|
35
|
+
if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
|
36
|
+
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
|
37
|
+
then
|
38
|
+
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
|
39
|
+
|
40
|
+
if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
|
41
|
+
then
|
42
|
+
. "${rvm_path:-$HOME/.rvm}/hooks/after_use"
|
43
|
+
fi
|
44
|
+
else
|
45
|
+
# If the environment file has not yet been created, use the RVM CLI to select.
|
46
|
+
if ! rvm --create "$environment_id"
|
47
|
+
then
|
48
|
+
echo "Failed to create RVM environment '${environment_id}'."
|
49
|
+
return 1
|
50
|
+
fi
|
51
|
+
fi
|
52
|
+
|
53
|
+
#
|
54
|
+
# If you use an RVM gemset file to install a list of gems (*.gems), you can have
|
55
|
+
# it be automatically loaded. Uncomment the following and adjust the filename if
|
56
|
+
# necessary.
|
57
|
+
#
|
58
|
+
# filename=".gems"
|
59
|
+
# if [[ -s "$filename" ]]
|
60
|
+
# then
|
61
|
+
# rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
|
62
|
+
# fi
|
63
|
+
|
64
|
+
# If you use bundler, this might be useful to you:
|
65
|
+
# if [[ -s Gemfile ]] && ! command -v bundle >/dev/null
|
66
|
+
# then
|
67
|
+
# printf "%b" "The rubygem 'bundler' is not installed. Installing it now.\n"
|
68
|
+
# gem install bundler
|
69
|
+
# fi
|
70
|
+
# if [[ -s Gemfile ]] && command -v bundle
|
71
|
+
# then
|
72
|
+
# bundle install
|
73
|
+
# fi
|
74
|
+
|
75
|
+
if [[ $- == *i* ]] # check for interactive shells
|
76
|
+
then
|
77
|
+
echo "Using: $(tput setaf 2)$GEM_HOME$(tput sgr0)" # show the user the ruby and gemset they are using in green
|
78
|
+
else
|
79
|
+
echo "Using: $GEM_HOME" # don't use colors in interactive shells
|
80
|
+
fi
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
rate_limiter (0.0.1)
|
5
|
+
activerecord (~> 3.0)
|
6
|
+
railties (~> 3.0)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: http://rubygems.org/
|
10
|
+
specs:
|
11
|
+
actionpack (3.2.9)
|
12
|
+
activemodel (= 3.2.9)
|
13
|
+
activesupport (= 3.2.9)
|
14
|
+
builder (~> 3.0.0)
|
15
|
+
erubis (~> 2.7.0)
|
16
|
+
journey (~> 1.0.4)
|
17
|
+
rack (~> 1.4.0)
|
18
|
+
rack-cache (~> 1.2)
|
19
|
+
rack-test (~> 0.6.1)
|
20
|
+
sprockets (~> 2.2.1)
|
21
|
+
activemodel (3.2.9)
|
22
|
+
activesupport (= 3.2.9)
|
23
|
+
builder (~> 3.0.0)
|
24
|
+
activerecord (3.2.9)
|
25
|
+
activemodel (= 3.2.9)
|
26
|
+
activesupport (= 3.2.9)
|
27
|
+
arel (~> 3.0.2)
|
28
|
+
tzinfo (~> 0.3.29)
|
29
|
+
activesupport (3.2.9)
|
30
|
+
i18n (~> 0.6)
|
31
|
+
multi_json (~> 1.0)
|
32
|
+
arel (3.0.2)
|
33
|
+
builder (3.0.4)
|
34
|
+
diff-lcs (1.1.3)
|
35
|
+
erubis (2.7.0)
|
36
|
+
hike (1.2.1)
|
37
|
+
i18n (0.6.1)
|
38
|
+
journey (1.0.4)
|
39
|
+
json (1.7.5)
|
40
|
+
multi_json (1.5.0)
|
41
|
+
rack (1.4.1)
|
42
|
+
rack-cache (1.2)
|
43
|
+
rack (>= 0.4)
|
44
|
+
rack-ssl (1.3.2)
|
45
|
+
rack
|
46
|
+
rack-test (0.6.2)
|
47
|
+
rack (>= 1.0)
|
48
|
+
railties (3.2.9)
|
49
|
+
actionpack (= 3.2.9)
|
50
|
+
activesupport (= 3.2.9)
|
51
|
+
rack-ssl (~> 1.3.2)
|
52
|
+
rake (>= 0.8.7)
|
53
|
+
rdoc (~> 3.4)
|
54
|
+
thor (>= 0.14.6, < 2.0)
|
55
|
+
rake (10.0.2)
|
56
|
+
rdoc (3.12)
|
57
|
+
json (~> 1.4)
|
58
|
+
rspec (2.12.0)
|
59
|
+
rspec-core (~> 2.12.0)
|
60
|
+
rspec-expectations (~> 2.12.0)
|
61
|
+
rspec-mocks (~> 2.12.0)
|
62
|
+
rspec-core (2.12.1)
|
63
|
+
rspec-expectations (2.12.0)
|
64
|
+
diff-lcs (~> 1.1.3)
|
65
|
+
rspec-mocks (2.12.0)
|
66
|
+
sprockets (2.2.2)
|
67
|
+
hike (~> 1.2)
|
68
|
+
multi_json (~> 1.0)
|
69
|
+
rack (~> 1.0)
|
70
|
+
tilt (~> 1.1, != 1.3.0)
|
71
|
+
thor (0.16.0)
|
72
|
+
tilt (1.3.3)
|
73
|
+
tzinfo (0.3.35)
|
74
|
+
|
75
|
+
PLATFORMS
|
76
|
+
ruby
|
77
|
+
|
78
|
+
DEPENDENCIES
|
79
|
+
rate_limiter!
|
80
|
+
rspec
|
data/README.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# rate_limiter
|
2
|
+
|
3
|
+
A gem that limits the rate at which ActiveRecord model instances can be created.
|
4
|
+
|
5
|
+
## Rails Version
|
6
|
+
|
7
|
+
This gem has only been tested on Rails 3.2. There is no reason that I am aware of that would prevent it from working on all versions of Rails 3 (and Rails 4 when it is released).
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add the gem to your project's Gemfile:
|
12
|
+
|
13
|
+
gem 'rate_limiter'
|
14
|
+
|
15
|
+
## Basic Usage
|
16
|
+
|
17
|
+
In the models you want to rate limit simply call the `rate_limit` method inside the model.
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
class ProductReview < ActiveRecord::Base
|
21
|
+
rate_limit
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
By default this will rate limit creation of instances using the `ip_address` attribute with an interval of one minute. This is kind of a bold assumption (that may change in future versions) since there's a good chance you don't have an `ip_address` attribute on your model. If that's the case then you can do the following:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
class ProductReview < ActiveRecord::Base
|
29
|
+
rate_limit :on => :username
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
This will instead check for `ProductReview`s with a matching `username` instead.
|
34
|
+
|
35
|
+
Because you may want to increase or decrease the interval between creating instances of your model you can do this:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
class ProductReview < ActiveRecord::Base
|
39
|
+
rate_limit :interval => 3.hours
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
## Contributing
|
44
|
+
|
45
|
+
If you feel like you can add something useful to rate_limiter then don't hesitate to contribute! To make sure your fix/feature has a high chance of being included, please do the following:
|
46
|
+
|
47
|
+
1. Fork the repo.
|
48
|
+
|
49
|
+
2. Run the tests. I will only take pull requests with passing tests, and it's great to know that you have a clean slate: `bundle && rake`
|
50
|
+
|
51
|
+
3. Add a test for your change. Only adding tests for existing code, refactoring, and documentation changes require no new tests. If you are adding functionality or fixing a bug, you need a test!
|
52
|
+
|
53
|
+
4. Make the test pass.
|
54
|
+
|
55
|
+
5. Push to your fork and submit a pull request.
|
56
|
+
|
57
|
+
I can't guarantee that I will accept the change, but if I don't I will be sure to let you know why!
|
58
|
+
|
59
|
+
Some things that will increase the chance that your pull request is accepted, taken straight from the Ruby on Rails guide:
|
60
|
+
|
61
|
+
* Use Rails idioms and helpers
|
62
|
+
* Include tests that fail without your code, and pass with it
|
63
|
+
* Update the documentation, guides, or whatever is affected by your contribution
|
64
|
+
|
65
|
+
Yes, I am well aware of the irony of asking for tests when there are effectively none right now. This gem is a work in progress.
|
data/Rakefile
ADDED
data/lib/rate_limiter.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
require 'rate_limiter/config'
|
4
|
+
require 'rate_limiter/model'
|
5
|
+
|
6
|
+
module RateLimiter
|
7
|
+
def self.timestamp_field= field_name
|
8
|
+
RateLimiter.config.timestamp_field = field_name
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.timestamp_field
|
12
|
+
RateLimiter.config.timestamp_field
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def self.config
|
18
|
+
@@config ||= RateLimiter::Config.instance
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
ActiveSupport.on_load(:active_record) do
|
23
|
+
include RateLimiter::Model
|
24
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module RateLimiter
|
2
|
+
module Model
|
3
|
+
def self.included(base)
|
4
|
+
base.send :extend, ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def rate_limit options = {}
|
9
|
+
send :include, InstanceMethods
|
10
|
+
|
11
|
+
class_attribute :rate_limit_on
|
12
|
+
self.rate_limit_on = options[:on] || :ip_address
|
13
|
+
|
14
|
+
class_attribute :rate_limit_interval
|
15
|
+
self.rate_limit_interval = options[:interval] || 1.minute
|
16
|
+
|
17
|
+
class_attribute :rate_limit_if_condition
|
18
|
+
self.rate_limit_if_condition = options[:if]
|
19
|
+
|
20
|
+
class_attribute :rate_limit_unless_condition
|
21
|
+
self.rate_limit_unless_condition = options[:unless]
|
22
|
+
|
23
|
+
self.before_create :check_rate_limit
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module InstanceMethods
|
28
|
+
def check_rate_limit
|
29
|
+
if rate_limit?
|
30
|
+
klass = self.class
|
31
|
+
|
32
|
+
others = klass.where("#{klass.rate_limit_on} =? AND created_at >= ?", self.send(klass.rate_limit_on), Time.now - klass.rate_limit_interval)
|
33
|
+
|
34
|
+
if others.present?
|
35
|
+
# TODO: Come up with a better error message.
|
36
|
+
self.errors.add(:base, "You cannot create a new #{klass.name.downcase} yet.")
|
37
|
+
|
38
|
+
false
|
39
|
+
else
|
40
|
+
true
|
41
|
+
end
|
42
|
+
else
|
43
|
+
true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def rate_limit?
|
48
|
+
(rate_limit_if_condition.blank? || rate_limit_if_condition.call(self)) && !rate_limit_unless_condition.try(:call, self)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'rate_limiter/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'rate_limiter'
|
7
|
+
s.version = RateLimiter::VERSION
|
8
|
+
s.authors = ['Sean Eshbaugh']
|
9
|
+
s.email = ['seaneshbaugh@gmail.com']
|
10
|
+
s.homepage = 'https://github.com/seaneshbaugh/rate_limiter'
|
11
|
+
s.summary = 'Adds creation rate limiting to ActiveRecord models.'
|
12
|
+
s.description = 'Adds creation rate limiting to ActiveRecord models.'
|
13
|
+
|
14
|
+
s.rubyforge_project = 'rate_limiter'
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ['lib']
|
20
|
+
|
21
|
+
s.add_dependency 'railties', '~> 3.0'
|
22
|
+
s.add_dependency 'activerecord', '~> 3.0'
|
23
|
+
|
24
|
+
s.add_development_dependency 'rspec'
|
25
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rate_limiter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sean Eshbaugh
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: railties
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: activerecord
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '3.0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '3.0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Adds creation rate limiting to ActiveRecord models.
|
63
|
+
email:
|
64
|
+
- seaneshbaugh@gmail.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- .rspec
|
71
|
+
- .rvmrc
|
72
|
+
- CHANGELOG.md
|
73
|
+
- Gemfile
|
74
|
+
- Gemfile.lock
|
75
|
+
- README.md
|
76
|
+
- Rakefile
|
77
|
+
- lib/rate_limiter.rb
|
78
|
+
- lib/rate_limiter/config.rb
|
79
|
+
- lib/rate_limiter/model.rb
|
80
|
+
- lib/rate_limiter/version.rb
|
81
|
+
- rate_limiter.gemspec
|
82
|
+
- spec/rate_limiter/rate_limiter_spec.rb
|
83
|
+
- spec/spec_helper.rb
|
84
|
+
homepage: https://github.com/seaneshbaugh/rate_limiter
|
85
|
+
licenses: []
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project: rate_limiter
|
104
|
+
rubygems_version: 1.8.24
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: Adds creation rate limiting to ActiveRecord models.
|
108
|
+
test_files:
|
109
|
+
- spec/rate_limiter/rate_limiter_spec.rb
|
110
|
+
- spec/spec_helper.rb
|