pause 0.2.1 → 0.5.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.
- checksums.yaml +5 -5
- data/.github/workflows/rspec.yml +31 -0
- data/.github/workflows/rubocop.yml +31 -0
- data/.gitignore +2 -1
- data/.rubocop.yml +28 -0
- data/.rubocop_todo.yml +192 -0
- data/Gemfile +16 -9
- data/Gemfile.lock +145 -0
- data/Guardfile +7 -6
- data/LICENSE.txt +1 -1
- data/README.md +152 -27
- data/Rakefile +44 -2
- data/bin/spec +14 -0
- data/lib/pause/action.rb +116 -94
- data/lib/pause/analyzer.rb +7 -3
- data/lib/pause/configuration.rb +4 -2
- data/lib/pause/helper/timing.rb +2 -0
- data/lib/pause/logger.rb +12 -5
- data/lib/pause/rate_limited_event.rb +2 -1
- data/lib/pause/redis/adapter.rb +41 -19
- data/lib/pause/redis/sharded_adapter.rb +12 -16
- data/lib/pause/version.rb +3 -1
- data/lib/pause.rb +16 -13
- data/pause.gemspec +18 -12
- data/spec/pause/action_spec.rb +168 -98
- data/spec/pause/analyzer_spec.rb +9 -10
- data/spec/pause/configuration_spec.rb +6 -5
- data/spec/pause/logger_spec.rb +25 -0
- data/spec/pause/pause_spec.rb +6 -4
- data/spec/pause/redis/adapter_spec.rb +56 -31
- data/spec/pause/redis/sharded_adapter_spec.rb +19 -5
- data/spec/spec_helper.rb +24 -11
- metadata +39 -27
- data/.travis.yml +0 -10
- data/spec/support/fakeredis.rb +0 -2
data/spec/pause/analyzer_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
require 'timecop'
|
3
5
|
|
@@ -11,20 +13,17 @@ describe Pause::Analyzer do
|
|
11
13
|
end
|
12
14
|
|
13
15
|
let(:resolution) { 10 }
|
16
|
+
let(:analyzer) { Pause.analyzer }
|
17
|
+
let(:action) { FollowPushNotification.new('1243123') }
|
14
18
|
let(:history) { 60 }
|
15
19
|
let(:configuration) { Pause::Configuration.new }
|
16
20
|
let(:adapter) { Pause::Redis::Adapter.new(configuration) }
|
17
21
|
|
18
22
|
before do
|
19
|
-
allow(Pause).to
|
20
|
-
allow(Pause
|
21
|
-
allow(Pause.config).to receive(:history).and_return(history)
|
22
|
-
allow(Pause).to receive(:adapter).and_return(adapter)
|
23
|
+
allow(Pause.config).to receive_messages(resolution: resolution, history: history)
|
24
|
+
allow(Pause).to receive_messages(config: configuration, adapter: adapter)
|
23
25
|
end
|
24
26
|
|
25
|
-
let(:analyzer) { Pause.analyzer }
|
26
|
-
let(:action) { FollowPushNotification.new('1243123') }
|
27
|
-
|
28
27
|
describe '#analyze' do
|
29
28
|
it 'checks and blocks if max_allowed is reached' do
|
30
29
|
time = Time.now
|
@@ -39,11 +38,11 @@ describe Pause::Analyzer do
|
|
39
38
|
end
|
40
39
|
|
41
40
|
describe '#check' do
|
42
|
-
it '
|
43
|
-
expect(analyzer.check(action)).to
|
41
|
+
it 'returns nil if action is NOT blocked' do
|
42
|
+
expect(analyzer.check(action)).to be_nil
|
44
43
|
end
|
45
44
|
|
46
|
-
it '
|
45
|
+
it 'returns blocked action if action is blocked' do
|
47
46
|
Timecop.freeze Time.now do
|
48
47
|
5.times do
|
49
48
|
action.increment!
|
@@ -1,10 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Pause::Configuration, '#configure' do
|
6
|
+
subject { described_class.new }
|
4
7
|
|
5
|
-
|
6
|
-
|
7
|
-
it 'should allow configuration via block' do
|
8
|
+
it 'allows configuration via block' do
|
8
9
|
subject.configure do |c|
|
9
10
|
c.redis_host = '128.23.12.8'
|
10
11
|
c.redis_port = '2134'
|
@@ -24,7 +25,7 @@ describe Pause::Configuration, '#configure' do
|
|
24
25
|
expect(subject.sharded).to be true
|
25
26
|
end
|
26
27
|
|
27
|
-
it '
|
28
|
+
it 'provides redis defaults' do
|
28
29
|
subject.configure do |config|
|
29
30
|
# do nothing
|
30
31
|
end
|
@@ -33,7 +34,7 @@ describe Pause::Configuration, '#configure' do
|
|
33
34
|
expect(subject.redis_port).to eq(6379)
|
34
35
|
expect(subject.redis_db).to eq('1')
|
35
36
|
expect(subject.resolution).to eq(600) # 10 minutes
|
36
|
-
expect(subject.history).to eq(
|
37
|
+
expect(subject.history).to eq(86_400) # one day
|
37
38
|
expect(subject.sharded).to be false
|
38
39
|
end
|
39
40
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Pause::Logger do
|
6
|
+
describe 'when accessed #puts' do
|
7
|
+
before do
|
8
|
+
expect($stdout).to receive(:puts).with('hello')
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'calls through to puts without color' do
|
12
|
+
described_class.puts('hello')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'when accessed via #fatal' do
|
17
|
+
before do
|
18
|
+
expect($stderr).to receive(:puts).with("\e[31mwhoops\e[0m")
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'calls through to puts with color' do
|
22
|
+
described_class.fatal('whoops')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/spec/pause/pause_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
RSpec.describe Pause do
|
@@ -5,8 +7,8 @@ RSpec.describe Pause do
|
|
5
7
|
let(:configuration) { Pause::Configuration.new }
|
6
8
|
|
7
9
|
before do
|
8
|
-
|
9
|
-
allow(
|
10
|
+
described_class.adapter = nil
|
11
|
+
allow(described_class).to receive(:config).and_return(configuration)
|
10
12
|
configuration.configure { |c| c.sharded = sharded }
|
11
13
|
end
|
12
14
|
|
@@ -14,7 +16,7 @@ RSpec.describe Pause do
|
|
14
16
|
let(:sharded) { true }
|
15
17
|
|
16
18
|
it 'is a ShardedAdapter' do
|
17
|
-
expect(
|
19
|
+
expect(described_class.adapter).to be_a(Pause::Redis::ShardedAdapter)
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
@@ -22,7 +24,7 @@ RSpec.describe Pause do
|
|
22
24
|
let(:sharded) { false }
|
23
25
|
|
24
26
|
it 'is an Adapter' do
|
25
|
-
expect(
|
27
|
+
expect(described_class.adapter).to be_a(Pause::Redis::Adapter)
|
26
28
|
end
|
27
29
|
end
|
28
30
|
end
|
@@ -1,52 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
require 'date'
|
3
5
|
require 'timecop'
|
4
6
|
|
5
7
|
describe Pause::Redis::Adapter do
|
6
|
-
|
7
8
|
let(:resolution) { 10 }
|
9
|
+
let(:adapter) { described_class.new(Pause.config) }
|
10
|
+
let(:redis_conn) { adapter.send(:redis) }
|
8
11
|
let(:history) { 60 }
|
9
12
|
let(:configuration) { Pause::Configuration.new }
|
10
13
|
|
11
14
|
before do
|
12
15
|
allow(Pause).to receive(:config).and_return(configuration)
|
13
|
-
allow(Pause.config).to
|
14
|
-
allow(Pause.config).to receive(:history).and_return(history)
|
16
|
+
allow(Pause.config).to receive_messages(resolution: resolution, history: history)
|
15
17
|
redis_conn.flushall
|
16
18
|
end
|
17
19
|
|
18
|
-
let(:adapter) { Pause::Redis::Adapter.new(Pause.config) }
|
19
|
-
let(:redis_conn) { adapter.send(:redis) }
|
20
|
-
|
21
20
|
describe '#increment' do
|
22
21
|
let(:scope) { 'blah' }
|
23
22
|
let(:identifier) { '213213' }
|
24
|
-
let(:tracked_key) {
|
23
|
+
let(:tracked_key) { "i:#{scope}:|#{identifier}|" }
|
24
|
+
let(:now) { Time.now.to_i }
|
25
25
|
|
26
|
-
it '
|
26
|
+
it 'adds key to a redis set' do
|
27
27
|
adapter.increment(scope, identifier, Time.now.to_i)
|
28
|
-
set = redis_conn.zrange(tracked_key, 0, -1, :
|
29
|
-
expect(set).
|
30
|
-
expect(set.size).to
|
31
|
-
expect(set[0].size).to
|
28
|
+
set = redis_conn.zrange(tracked_key, 0, -1, with_scores: true)
|
29
|
+
expect(set).not_to be_empty
|
30
|
+
expect(set.size).to be(1)
|
31
|
+
expect(set[0].size).to be(2)
|
32
32
|
end
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
describe 'when increment is called' do
|
35
|
+
let(:redis_conn_double) { instance_double(Redis) }
|
36
|
+
|
37
|
+
before do
|
38
|
+
allow(adapter).to receive(:with_multi).and_yield(redis_conn_double)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'calls zincrby on the redis connection' do
|
42
|
+
allow(redis_conn_double).to receive(:expire)
|
43
|
+
expect(redis_conn_double).to receive(:zincrby)
|
37
44
|
|
38
|
-
adapter.time_blocks_to_keep = 1
|
39
|
-
Timecop.freeze time do
|
40
45
|
adapter.increment(scope, identifier, Time.now.to_i)
|
41
46
|
end
|
42
|
-
|
47
|
+
|
48
|
+
it 'calls expire on the redis key' do
|
49
|
+
expect(redis_conn_double).to receive(:expire).with(tracked_key, history)
|
50
|
+
allow(redis_conn_double).to receive(:zincrby)
|
51
|
+
|
43
52
|
adapter.increment(scope, identifier, Time.now.to_i)
|
44
53
|
end
|
45
54
|
end
|
46
55
|
|
47
|
-
|
48
|
-
|
49
|
-
adapter.
|
56
|
+
context 'removing two elements' do
|
57
|
+
let(:to_delete) { 2 }
|
58
|
+
let(:period_start) { adapter.period_marker(resolution, now) }
|
59
|
+
let(:period_end) { adapter.period_marker(resolution, now + resolution) }
|
60
|
+
|
61
|
+
around do |example|
|
62
|
+
Timecop.freeze(now) { example.run }
|
63
|
+
end
|
64
|
+
|
65
|
+
before do
|
66
|
+
redis_conn.flushall
|
67
|
+
adapter.time_blocks_to_keep = 1
|
68
|
+
allow(redis_conn).to receive(:zrem).with(tracked_key, [period_start])
|
69
|
+
allow(redis_conn).to receive(:zrem).with(tracked_key, [period_start, period_end])
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'removes old elements' do
|
73
|
+
adapter.increment(scope, identifier, now.to_i)
|
74
|
+
to_delete.times do |t|
|
75
|
+
next_time = now + (adapter.resolution + t + 1)
|
76
|
+
adapter.increment(scope, identifier, next_time.to_i)
|
77
|
+
end
|
78
|
+
end
|
50
79
|
end
|
51
80
|
end
|
52
81
|
|
@@ -68,21 +97,18 @@ describe Pause::Redis::Adapter do
|
|
68
97
|
|
69
98
|
adapter.expire_block_list(scope)
|
70
99
|
|
71
|
-
expect(redis_conn.zscore('b:|a|', blocked_identifier)).not_to
|
72
|
-
expect(redis_conn.zscore('b:|a|', expired_identifier)).to
|
100
|
+
expect(redis_conn.zscore('b:|a|', blocked_identifier)).not_to be_nil
|
101
|
+
expect(redis_conn.zscore('b:|a|', expired_identifier)).to be_nil
|
73
102
|
end
|
74
103
|
end
|
75
104
|
|
76
|
-
describe '#rate_limit!' do
|
77
|
-
end
|
78
|
-
|
79
105
|
describe '#rate_limited?' do
|
80
106
|
let(:scope) { 'ipn:follow' }
|
81
107
|
let(:identifier) { '123461234' }
|
82
108
|
let(:blocked_key) { "b:#{key}" }
|
83
|
-
let(:ttl) {
|
109
|
+
let(:ttl) { 110_000 }
|
84
110
|
|
85
|
-
it '
|
111
|
+
it 'returns true if blocked' do
|
86
112
|
adapter.rate_limit!(scope, identifier, ttl)
|
87
113
|
expect(adapter.rate_limited?(scope, identifier)).to be true
|
88
114
|
end
|
@@ -122,17 +148,16 @@ describe Pause::Redis::Adapter do
|
|
122
148
|
let(:scope) { 'ipn:follow' }
|
123
149
|
let(:identifier) { '1234' }
|
124
150
|
let(:blocked_key) { "b:|#{scope}|" }
|
125
|
-
let(:ttl) {
|
151
|
+
let(:ttl) { 110_000 }
|
126
152
|
|
127
153
|
it 'saves ip to redis with expiration' do
|
128
154
|
time = Time.now
|
129
155
|
Timecop.freeze time do
|
130
156
|
adapter.rate_limit!(scope, identifier, ttl)
|
131
157
|
end
|
132
|
-
expect(redis_conn.zscore(blocked_key, identifier)).
|
158
|
+
expect(redis_conn.zscore(blocked_key, identifier)).not_to be_nil
|
133
159
|
expect(redis_conn.zscore(blocked_key, identifier)).to eq(time.to_i + ttl)
|
134
160
|
end
|
135
|
-
|
136
161
|
end
|
137
162
|
end
|
138
163
|
|
@@ -1,24 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
require 'date'
|
3
5
|
require 'timecop'
|
4
6
|
|
5
7
|
describe Pause::Redis::ShardedAdapter do
|
6
|
-
|
7
8
|
let(:resolution) { 10 }
|
9
|
+
let(:adapter) { described_class.new(Pause.config) }
|
8
10
|
let(:history) { 60 }
|
9
11
|
let(:configuration) { Pause::Configuration.new }
|
10
12
|
|
11
13
|
before do
|
12
14
|
allow(Pause).to receive(:config).and_return(configuration)
|
13
|
-
allow(Pause.config).to
|
14
|
-
allow(Pause.config).to receive(:history).and_return(history)
|
15
|
+
allow(Pause.config).to receive_messages(resolution: resolution, history: history)
|
15
16
|
end
|
16
17
|
|
17
|
-
let(:adapter) { Pause::Redis::ShardedAdapter.new(Pause.config) }
|
18
|
-
|
19
18
|
describe '#all_keys' do
|
20
19
|
it 'is not supported' do
|
21
20
|
expect { adapter.all_keys('cake') }.to raise_error(Pause::Redis::OperationNotSupported)
|
22
21
|
end
|
23
22
|
end
|
23
|
+
|
24
|
+
describe '#with_multi' do
|
25
|
+
let(:redis) { adapter.send(:redis) }
|
26
|
+
|
27
|
+
it 'does not call redis.multi' do
|
28
|
+
expect(redis).not_to receive(:multi)
|
29
|
+
expect { adapter.increment(:scope, 123, Time.now) }.not_to raise_error
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#redis' do
|
34
|
+
it 'does not use redis db when connecting' do
|
35
|
+
expect(adapter.send(:redis_connection_opts)).not_to include(:db)
|
36
|
+
end
|
37
|
+
end
|
24
38
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
4
|
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
5
|
# Require this file using `require "spec_helper"` to ensure that it is only
|
@@ -5,20 +7,31 @@
|
|
5
7
|
#
|
6
8
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
require '
|
10
|
+
require 'fileutils'
|
11
|
+
|
12
|
+
require 'simplecov'
|
13
|
+
SimpleCov.start
|
14
|
+
|
11
15
|
require 'pause'
|
12
|
-
|
13
|
-
|
16
|
+
|
17
|
+
if ENV['PAUSE_REAL_REDIS']
|
18
|
+
require 'pause/redis/adapter'
|
19
|
+
puts
|
20
|
+
puts "NOTE: Using real Redis-server at #{Pause::Redis::Adapter.redis.inspect}\n\n"
|
21
|
+
else
|
22
|
+
require 'fakeredis/rspec'
|
23
|
+
end
|
14
24
|
|
15
25
|
RSpec.configure do |config|
|
16
|
-
|
17
|
-
|
26
|
+
rspec_dir = './.spec'
|
27
|
+
FileUtils.mkdir_p(rspec_dir)
|
28
|
+
config.example_status_persistence_file_path = "#{rspec_dir}/results.txt"
|
18
29
|
|
19
|
-
# Run specs in random order to surface order dependencies. If you find an
|
20
|
-
# order dependency and want to debug it, you can fix the order by providing
|
21
|
-
# the seed, which is printed after each run.
|
22
|
-
# --seed 1234
|
23
30
|
config.order = 'random'
|
31
|
+
|
32
|
+
if ENV['PAUSE_REAL_REDIS']
|
33
|
+
config.before do
|
34
|
+
Pause::Redis::Adapter.redis.flushdb
|
35
|
+
end
|
36
|
+
end
|
24
37
|
end
|
metadata
CHANGED
@@ -1,18 +1,31 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pause
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
+
- Konstantin Gredeskoul
|
7
8
|
- Atasay Gokkaya
|
8
9
|
- Paul Henry
|
9
10
|
- Eric Saxby
|
10
|
-
- Konstantin Gredeskoul
|
11
|
-
autorequire:
|
12
11
|
bindir: bin
|
13
12
|
cert_chain: []
|
14
|
-
date:
|
13
|
+
date: 2025-02-07 00:00:00.000000000 Z
|
15
14
|
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: colored2
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ">="
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '0'
|
16
29
|
- !ruby/object:Gem::Dependency
|
17
30
|
name: redis
|
18
31
|
requirement: !ruby/object:Gem::Requirement
|
@@ -27,26 +40,34 @@ dependencies:
|
|
27
40
|
- - ">="
|
28
41
|
- !ruby/object:Gem::Version
|
29
42
|
version: '0'
|
30
|
-
description:
|
31
|
-
|
43
|
+
description: This gem provides highly flexible and easy to use interface to define
|
44
|
+
rate limit checks, register events as they come, and verify if the rate limit is
|
45
|
+
reached. Multiple checks for the same metric are easily supported. This gem is used
|
46
|
+
at very high scale on several popular web sites.
|
32
47
|
email:
|
48
|
+
- kigster@gmail.com
|
33
49
|
- atasay@wanelo.com
|
34
50
|
- paul@wanelo.com
|
35
|
-
- sax@
|
36
|
-
|
37
|
-
|
51
|
+
- sax@ericsaxby.com
|
52
|
+
executables:
|
53
|
+
- spec
|
38
54
|
extensions: []
|
39
55
|
extra_rdoc_files: []
|
40
56
|
files:
|
57
|
+
- ".github/workflows/rspec.yml"
|
58
|
+
- ".github/workflows/rubocop.yml"
|
41
59
|
- ".gitignore"
|
42
60
|
- ".rspec"
|
61
|
+
- ".rubocop.yml"
|
62
|
+
- ".rubocop_todo.yml"
|
43
63
|
- ".rvmrc"
|
44
|
-
- ".travis.yml"
|
45
64
|
- Gemfile
|
65
|
+
- Gemfile.lock
|
46
66
|
- Guardfile
|
47
67
|
- LICENSE.txt
|
48
68
|
- README.md
|
49
69
|
- Rakefile
|
70
|
+
- bin/spec
|
50
71
|
- lib/pause.rb
|
51
72
|
- lib/pause/action.rb
|
52
73
|
- lib/pause/analyzer.rb
|
@@ -61,15 +82,15 @@ files:
|
|
61
82
|
- spec/pause/action_spec.rb
|
62
83
|
- spec/pause/analyzer_spec.rb
|
63
84
|
- spec/pause/configuration_spec.rb
|
85
|
+
- spec/pause/logger_spec.rb
|
64
86
|
- spec/pause/pause_spec.rb
|
65
87
|
- spec/pause/redis/adapter_spec.rb
|
66
88
|
- spec/pause/redis/sharded_adapter_spec.rb
|
67
89
|
- spec/spec_helper.rb
|
68
|
-
|
69
|
-
homepage: https://github.com/wanelo/pause
|
90
|
+
homepage: https://github.com/kigster/pause
|
70
91
|
licenses: []
|
71
|
-
metadata:
|
72
|
-
|
92
|
+
metadata:
|
93
|
+
rubygems_mfa_required: 'true'
|
73
94
|
rdoc_options: []
|
74
95
|
require_paths:
|
75
96
|
- lib
|
@@ -84,17 +105,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
105
|
- !ruby/object:Gem::Version
|
85
106
|
version: '0'
|
86
107
|
requirements: []
|
87
|
-
|
88
|
-
rubygems_version: 2.2.2
|
89
|
-
signing_key:
|
108
|
+
rubygems_version: 3.6.3
|
90
109
|
specification_version: 4
|
91
|
-
summary:
|
92
|
-
|
93
|
-
|
94
|
-
- spec/pause/analyzer_spec.rb
|
95
|
-
- spec/pause/configuration_spec.rb
|
96
|
-
- spec/pause/pause_spec.rb
|
97
|
-
- spec/pause/redis/adapter_spec.rb
|
98
|
-
- spec/pause/redis/sharded_adapter_spec.rb
|
99
|
-
- spec/spec_helper.rb
|
100
|
-
- spec/support/fakeredis.rb
|
110
|
+
summary: Fast, scalable, and flexible real time rate limiting library for distributed
|
111
|
+
Ruby environments backed by Redis.
|
112
|
+
test_files: []
|
data/.travis.yml
DELETED
data/spec/support/fakeredis.rb
DELETED