Skip to content

Commit 5dcafe0

Browse files
authored
Merge pull request #2 from prysmex/v0.3.3
cleaner api, ensure jobs run after failing
2 parents ed86349 + 59a8d54 commit 5dcafe0

File tree

9 files changed

+112
-67
lines changed

9 files changed

+112
-67
lines changed

.rubocop.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ require:
1818

1919
AllCops:
2020
NewCops: enable
21-
# TargetRubyVersion: 2.7.8
21+
TargetRubyVersion: 3.2.2
2222
# TargetRailsVersion: 6.1.4
2323
# Exclude:
2424
# - 'Gemfile.lock'

.rubocop_todo.yml

+9-10
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
11
# This configuration was generated by
22
# `rubocop --auto-gen-config`
3-
# on 2024-03-19 18:55:09 UTC using RuboCop version 1.62.1.
3+
# on 2024-03-22 01:08:54 UTC using RuboCop version 1.62.1.
44
# The point is for the user to remove these configuration records
55
# one by one as the offenses are removed from the code base.
66
# Note that changes in the inspected code, or installation of new
77
# versions of RuboCop, may require this file to be generated again.
88

9+
# Offense count: 1
10+
# Configuration parameters: Severity, Include.
11+
# Include: **/*.gemspec
12+
Gemspec/RequiredRubyVersion:
13+
Exclude:
14+
- 'sidekiq-bouncer.gemspec'
15+
916
# Offense count: 1
1017
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
1118
Metrics/MethodLength:
1219
Max: 14
1320

14-
# Offense count: 1
15-
RSpec/BeforeAfterAll:
16-
Exclude:
17-
- '**/spec/spec_helper.rb'
18-
- '**/spec/rails_helper.rb'
19-
- '**/spec/support/**/*.rb'
20-
- 'spec/sidekiq_bouncer/bouncer_spec.rb'
21-
2221
# Offense count: 2
2322
# Configuration parameters: CountAsOne.
2423
RSpec/ExampleLength:
@@ -29,7 +28,7 @@ RSpec/ExampleLength:
2928
RSpec/NestedGroups:
3029
Max: 4
3130

32-
# Offense count: 1
31+
# Offense count: 2
3332
RSpec/SubjectStub:
3433
Exclude:
3534
- 'spec/sidekiq_bouncer/bouncer_spec.rb'

Gemfile

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ gemspec
99
gem 'debug', '>= 1.0.0'
1010
gem 'rake', '~> 13.1'
1111
gem 'rspec', '~> 3.9'
12+
gem 'timecop'
1213

1314
# rubocop
1415
gem 'rubocop', '~> 1.62'

README.md

+1-3
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,7 @@ class FooWorker
6161
# The default delay is 60 seconds. You can optionally override it.
6262
register_bouncer(delay: optional_delay_override, delay_buffer: optional_delay_buffer_override)
6363

64-
def perform(param1, param2, debounce_key = nil)
65-
return unless self.class.bouncer.let_in?(debounce_key) # last argument is added as debounce_key
66-
64+
def perform(param1, param2)
6765
# Do your thing.
6866
end
6967
end

lib/sidekiq_bouncer/bounceable.rb

+11-8
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,26 @@ module SidekiqBouncer
44
module Bounceable
55

66
def self.included(base)
7-
base.include InstanceMethods
7+
base.prepend InstanceMethods
88
base.extend ClassMethods
99
end
1010

1111
module ClassMethods
12-
# creates and sets a +SidekiqBouncer::Bouncer+
13-
def register_bouncer(**kwargs)
14-
@bouncer = SidekiqBouncer::Bouncer.new(self, **kwargs)
15-
end
16-
1712
# @retrun [SidekiqBouncer::Bouncer]
18-
def bouncer
19-
@bouncer
13+
attr_reader :bouncer
14+
15+
# creates and sets a +SidekiqBouncer::Bouncer+
16+
def register_bouncer(**)
17+
@bouncer = SidekiqBouncer::Bouncer.new(self, **)
2018
end
2119
end
2220

2321
module InstanceMethods
22+
def perform(*, debounce_key, **)
23+
self.class.bouncer.run(debounce_key) do
24+
super(*, **)
25+
end
26+
end
2427
end
2528

2629
end

lib/sidekiq_bouncer/bouncer.rb

+12-4
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ def debounce(*params, key_or_args_indices:)
5454

5555
# Checks if job should be excecuted
5656
#
57+
# @param [NilClass|String] key
58+
# @return [False|*] true if should be excecuted
59+
def run(key)
60+
return false unless let_in?(key)
61+
62+
yield.tap do
63+
redis.call('DEL', key)
64+
end
65+
end
66+
5767
# @param [NilClass|String] key
5868
# @return [Boolean] true if should be excecuted
5969
def let_in?(key)
@@ -67,18 +77,16 @@ def let_in?(key)
6777
timestamp = redis.call('GET', key)
6878
return false if timestamp.nil? || now_i < timestamp.to_i
6979

70-
redis.call('DEL', key)
71-
7280
true
7381
end
7482

83+
private
84+
7585
# @return [RedisClient::Pooled]
7686
def redis
7787
SidekiqBouncer.config.redis
7888
end
7989

80-
private
81-
8290
# Builds a key based on arguments
8391
#
8492
# @param [Array] redis_params

lib/sidekiq_bouncer/version.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# frozen_string_literal: true
22

33
module SidekiqBouncer
4-
VERSION = '0.3.2'
4+
VERSION = '0.3.3'
55
end

spec/sidekiq_bouncer/bouncer_spec.rb

+74-40
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,33 @@
11
# frozen_string_literal: true
22

33
# Mock
4-
module RedisMock
4+
class RedisMock # rubocop:disable Lint/EmptyClass
55
end
66

77
# Mock
88
class WorkerMock
99
def self.bouncer
1010
SidekiqBouncer::Bouncer.new(self)
1111
end
12-
13-
# def self.perform_at(*args); end
1412
end
1513

1614
# Tests
1715
describe SidekiqBouncer::Bouncer do
1816
# careful, the bouncer instance is generally cached on the worker model
1917
subject(:bouncer) { WorkerMock.bouncer }
2018

21-
before :all do
19+
let(:redis) { SidekiqBouncer.config.redis }
20+
let(:worker_klass) { WorkerMock }
21+
let(:now) { Time.now.to_i }
22+
23+
before do
2224
SidekiqBouncer.configure do |config|
23-
config.redis = RedisMock
25+
config.redis = RedisMock.new
2426
end
25-
end
2627

27-
let(:redis) { RedisMock }
28-
let(:worker_klass) { WorkerMock }
29-
let(:now) { 100 }
28+
Timecop.freeze(Time.now)
3029

31-
before do
3230
# stubbing
33-
allow(bouncer).to receive(:now_i) { now }
3431
allow(redis).to receive(:call)
3532
allow(worker_klass).to receive(:perform_at)
3633
end
@@ -43,7 +40,6 @@ def self.bouncer
4340
it { expect(bouncer).to respond_to(:delay_buffer=) }
4441
it { expect(bouncer).to respond_to(:debounce) }
4542
it { expect(bouncer).to respond_to(:let_in?) }
46-
it { expect(bouncer).to respond_to(:redis) }
4743
end
4844

4945
describe '.new' do
@@ -82,7 +78,7 @@ def self.bouncer
8278
it 'sets scoped_key to Redis with delayed timestamp' do
8379
bouncer.debounce('test_param_1', 'test_param_2', key_or_args_indices: [0, 1])
8480

85-
expect(SidekiqBouncer.config.redis)
81+
expect(redis)
8682
.to have_received(:call)
8783
.with('SET', 'WorkerMock:test_param_1,test_param_2', now + bouncer.delay)
8884
end
@@ -101,7 +97,7 @@ def self.bouncer
10197
it 'sets scoped_key to Redis with delayed timestamp' do
10298
bouncer.debounce('test_param_1', 'test_param_2', key_or_args_indices: [0])
10399

104-
expect(SidekiqBouncer.config.redis)
100+
expect(redis)
105101
.to have_received(:call)
106102
.with('SET', 'WorkerMock:test_param_1', now + bouncer.delay)
107103
end
@@ -121,7 +117,7 @@ def self.bouncer
121117
describe '#let_in?' do
122118
context 'when key is nil' do
123119
it 'does not call redis' do
124-
expect(SidekiqBouncer.config.redis).not_to have_received(:call)
120+
expect(redis).not_to have_received(:call)
125121
end
126122

127123
it 'returns true' do
@@ -135,7 +131,7 @@ def self.bouncer
135131
it 'exec call on redis with GET' do
136132
bouncer.let_in?(key)
137133

138-
expect(SidekiqBouncer.config.redis)
134+
expect(redis)
139135
.to have_received(:call)
140136
.with('GET', key)
141137
end
@@ -145,14 +141,6 @@ def self.bouncer
145141
allow(redis).to receive(:call).with('GET', anything).and_return(now - 10)
146142
end
147143

148-
it 'exec call on redis with DEL' do
149-
bouncer.let_in?(key)
150-
151-
expect(SidekiqBouncer.config.redis)
152-
.to have_received(:call)
153-
.with('DEL', key)
154-
end
155-
156144
it 'returns true' do
157145
expect(bouncer.let_in?(key)).to be(true)
158146
end
@@ -163,14 +151,6 @@ def self.bouncer
163151
allow(redis).to receive(:call).with('GET', anything).and_return(Time.now + 10)
164152
end
165153

166-
it 'does not exec call on redis with DEL' do
167-
bouncer.let_in?(key)
168-
169-
expect(SidekiqBouncer.config.redis)
170-
.not_to have_received(:call)
171-
.with('DEL', anything)
172-
end
173-
174154
it 'returns false' do
175155
expect(bouncer.let_in?(key)).to be(false)
176156
end
@@ -181,18 +161,72 @@ def self.bouncer
181161
allow(redis).to receive(:call).with('GET', anything).and_return(nil)
182162
end
183163

184-
it 'does not exec call on redis with DEL' do
185-
bouncer.let_in?(key)
186-
187-
expect(SidekiqBouncer.config.redis)
188-
.not_to have_received(:call)
189-
.with('DEL', anything)
190-
end
191-
192164
it 'returns false' do
193165
expect(bouncer.let_in?(key)).to be(false)
194166
end
195167
end
196168
end
197169
end
170+
171+
describe '#run' do
172+
before do
173+
# stubbing
174+
allow(bouncer).to receive(:let_in?).with('do').and_return(true)
175+
allow(bouncer).to receive(:let_in?).with('do_not').and_return(false)
176+
end
177+
178+
context 'when let_in? returns false' do
179+
it 'returns false' do
180+
expect(bouncer.run('do_not')).to be(false)
181+
end
182+
183+
it 'does not yield' do
184+
expect { |b| bouncer.run('do_not', &b) }.not_to yield_control
185+
end
186+
187+
it 'does not exec call on redis with DEL' do
188+
bouncer.run('do_not') { '__test__' }
189+
190+
expect(redis)
191+
.not_to have_received(:call)
192+
.with('DEL', anything)
193+
end
194+
end
195+
196+
context 'when let_in? returns true' do
197+
it 'returns yield return' do
198+
expect(bouncer.run('do') { '__test__' }).to be('__test__')
199+
end
200+
201+
it 'yields' do
202+
expect { |b| bouncer.run('do', &b) }.to yield_control
203+
end
204+
205+
it 'exec call on redis with DEL' do
206+
bouncer.run('do') { '__test__' }
207+
208+
expect(redis)
209+
.to have_received(:call)
210+
.with('DEL', 'do')
211+
end
212+
end
213+
end
214+
215+
describe '#now_i' do
216+
it 'returns now as integer' do
217+
expect(bouncer.send(:now_i)).to be(now)
218+
end
219+
end
220+
221+
describe '#redis' do
222+
it 'returns' do
223+
expect(bouncer.send(:redis)).to be_a(RedisMock)
224+
end
225+
end
226+
227+
describe '#redis_key' do
228+
it 'returns now as integer' do
229+
expect(bouncer.send(:redis_key, 'test_key')).to eql('WorkerMock:test_key')
230+
end
231+
end
198232
end

spec/spec_helper.rb

+2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# frozen_string_literal: true
22

3+
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
34
require 'bundler/setup'
45
require 'sidekiq_bouncer'
6+
require 'timecop'
57
require 'debug'
68

79
RSpec.configure do |config|

0 commit comments

Comments
 (0)