reactor 0.15.1 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +26 -1
- data/lib/reactor/subscription.rb +5 -1
- data/lib/reactor/version.rb +1 -1
- data/lib/reactor/workers/concerns/configuration.rb +66 -0
- data/lib/reactor/workers/event_worker.rb +1 -51
- data/lib/reactor/workers/mailer_worker.rb +1 -40
- data/lib/reactor.rb +1 -0
- data/spec/models/concerns/subscribable_spec.rb +17 -4
- data/spec/spec_helper.rb +1 -0
- data/spec/support/shared_examples.rb +47 -0
- data/spec/workers/event_worker_spec.rb +5 -45
- data/spec/workers/mailer_worker_spec.rb +4 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 84d1960b3d89f85ef116fbf0e2d97d93972cfee9
|
4
|
+
data.tar.gz: d6a69705d1496e401a93f8dcd86ab18de483b220
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 46258859a0c80d002aa59b6e9c9ab382bbacae87219afe46c3507dcf613cd2f3382ec8bb8ebcedd02f1d8792390a55499dceadd4ade4ad0707c30af38503f0b8
|
7
|
+
data.tar.gz: 5b388408b18555bb93eda610c29eeef2d2e77798e9c529f3ca5544c9ce382a6d27671c88a2a8128e7c82ab6d3b427409a6555ea28240b4fca8a1035892bd3091
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -234,10 +234,35 @@ end
|
|
234
234
|
|
235
235
|
for your testing convenience.
|
236
236
|
|
237
|
+
|
238
|
+
### Production Deployments
|
239
|
+
|
240
|
+
TLDR; Everything is a Sidekiq::Job, so all the same gotchas apply with regard to removing & renaming jobs that may have a live reference sitting in the queue. (AKA, you'll start seeing 'const undefined' exceptions when the job gets picked up if you've already deleted/renamed the job code.)
|
241
|
+
|
242
|
+
#### Adding Events and Subscribers
|
243
|
+
|
244
|
+
This is as easy as write + deploy. Of course your events getting fired won't have a subscriber pick them up until the new subscriber code is deployed in your sidekiq instances, but that's not too surprising.
|
245
|
+
|
246
|
+
#### Removing Events and Subscribers
|
247
|
+
|
248
|
+
Removing an event is as simple as deleting the line of code that `publish`es it.
|
249
|
+
Removing a subscriber requires awareness of basic Sidekiq principles.
|
250
|
+
|
251
|
+
**Is the subscriber that you're deleting virtually guaranteed to have a worker for it sitting in the queue when your deletion is deployed?**
|
252
|
+
|
253
|
+
If yes -> deprecate your subscriber first to ensure there are no references left in Redis. This will prevent Reactor from enqueuing more workers for it and make it safe for you delete in a secondry deploy.
|
254
|
+
```
|
255
|
+
on_event :high_frequency_event, :do_something, deprecated: true
|
256
|
+
```
|
257
|
+
|
258
|
+
If no -> you can probably just delete the subscriber.
|
259
|
+
In the worst case scenario, you get some background exceptions for a job you didn't intend to have run anyway. Pick your poison.
|
260
|
+
|
261
|
+
|
237
262
|
## Contributing
|
238
263
|
|
239
264
|
1. Fork it
|
240
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
265
|
+
2. Create your feature/fix branch (`git checkout -b my-new-feature`)
|
241
266
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
242
267
|
4. Push to the branch (`git push origin my-new-feature`)
|
243
268
|
5. Create new Pull Request
|
data/lib/reactor/subscription.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
module Reactor
|
2
2
|
class Subscription
|
3
3
|
|
4
|
-
attr_reader :source, :event_name, :action, :handler_name, :delay, :async, :worker_class
|
4
|
+
attr_reader :source, :event_name, :action, :handler_name, :delay, :async, :worker_class,
|
5
|
+
:deprecated
|
5
6
|
|
6
7
|
def self.build_handler_name(event_name, handler_name_option = nil)
|
7
8
|
if handler_name_option
|
@@ -24,6 +25,7 @@ module Reactor
|
|
24
25
|
|
25
26
|
@delay = options[:delay].to_i
|
26
27
|
@async = determine_async(options)
|
28
|
+
@deprecated = !!options[:deprecated]
|
27
29
|
build_worker_class
|
28
30
|
end
|
29
31
|
|
@@ -82,6 +84,7 @@ module Reactor
|
|
82
84
|
self.action = subscription.action
|
83
85
|
self.async = subscription.async
|
84
86
|
self.delay = subscription.delay
|
87
|
+
self.deprecated = subscription.deprecated
|
85
88
|
end
|
86
89
|
end
|
87
90
|
|
@@ -92,6 +95,7 @@ module Reactor
|
|
92
95
|
self.action = subscription.action
|
93
96
|
self.delay = subscription.delay
|
94
97
|
self.async = subscription.async
|
98
|
+
self.deprecated = subscription.deprecated
|
95
99
|
end
|
96
100
|
end
|
97
101
|
|
data/lib/reactor/version.rb
CHANGED
@@ -0,0 +1,66 @@
|
|
1
|
+
module Reactor
|
2
|
+
module Workers
|
3
|
+
module Configuration
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include Sidekiq::Worker
|
8
|
+
|
9
|
+
CONFIG = [:source, :action, :async, :delay, :deprecated]
|
10
|
+
|
11
|
+
class_attribute *CONFIG
|
12
|
+
end
|
13
|
+
|
14
|
+
class_methods do
|
15
|
+
def configured?
|
16
|
+
CONFIG.all? {|field| !self.send(field).nil? }
|
17
|
+
end
|
18
|
+
|
19
|
+
def perform_where_needed(data)
|
20
|
+
if deprecated
|
21
|
+
return
|
22
|
+
elsif delay > 0
|
23
|
+
perform_in(delay, data)
|
24
|
+
elsif async
|
25
|
+
perform_async(data)
|
26
|
+
else
|
27
|
+
new.perform(data)
|
28
|
+
end
|
29
|
+
source
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def configured?
|
34
|
+
self.class.configured?
|
35
|
+
end
|
36
|
+
|
37
|
+
def perform(data)
|
38
|
+
raise_unconfigured! unless configured?
|
39
|
+
return :__perform_aborted__ unless should_perform?
|
40
|
+
event = Reactor::Event.new(data)
|
41
|
+
if action.is_a?(Symbol)
|
42
|
+
source.send(action, event)
|
43
|
+
else
|
44
|
+
action.call(event)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def should_perform?
|
49
|
+
if Reactor.test_mode?
|
50
|
+
Reactor.test_mode_subscriber_enabled? source
|
51
|
+
else
|
52
|
+
true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def raise_unconfigured!
|
59
|
+
settings = Hash[CONFIG.map {|s| [s, self.class.send(s)] }]
|
60
|
+
raise UnconfiguredWorkerError.new(
|
61
|
+
"#{self.class.name} is not properly configured! Here are the settings: #{settings}"
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -8,58 +8,8 @@ module Reactor
|
|
8
8
|
module Workers
|
9
9
|
class EventWorker
|
10
10
|
|
11
|
-
|
11
|
+
include Reactor::Workers::Configuration
|
12
12
|
|
13
|
-
CONFIG = [:source, :action, :async, :delay]
|
14
|
-
|
15
|
-
class_attribute *CONFIG
|
16
|
-
|
17
|
-
def self.configured?
|
18
|
-
CONFIG.all? {|field| !self.send(field).nil? }
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.perform_where_needed(data)
|
22
|
-
if delay > 0
|
23
|
-
perform_in(delay, data)
|
24
|
-
elsif async
|
25
|
-
perform_async(data)
|
26
|
-
else
|
27
|
-
new.perform(data)
|
28
|
-
end
|
29
|
-
source
|
30
|
-
end
|
31
|
-
|
32
|
-
def configured?
|
33
|
-
self.class.configured?
|
34
|
-
end
|
35
|
-
|
36
|
-
def perform(data)
|
37
|
-
raise_unconfigured! unless configured?
|
38
|
-
return :__perform_aborted__ unless should_perform?
|
39
|
-
event = Reactor::Event.new(data)
|
40
|
-
if action.is_a?(Symbol)
|
41
|
-
source.send(action, event)
|
42
|
-
else
|
43
|
-
action.call(event)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def should_perform?
|
48
|
-
if Reactor.test_mode?
|
49
|
-
Reactor.test_mode_subscriber_enabled? source
|
50
|
-
else
|
51
|
-
true
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
private
|
56
|
-
|
57
|
-
def raise_unconfigured!
|
58
|
-
settings = Hash[CONFIG.map {|s| [s, self.class.send(s)] }]
|
59
|
-
raise UnconfiguredWorkerError.new(
|
60
|
-
"#{self.class.name} is not properly configured! Here are the settings: #{settings}"
|
61
|
-
)
|
62
|
-
end
|
63
13
|
end
|
64
14
|
end
|
65
15
|
end
|
@@ -6,30 +6,7 @@ module Reactor
|
|
6
6
|
module Workers
|
7
7
|
class MailerWorker
|
8
8
|
|
9
|
-
include
|
10
|
-
|
11
|
-
CONFIG = [:source, :action, :async, :delay]
|
12
|
-
|
13
|
-
class_attribute *CONFIG
|
14
|
-
|
15
|
-
def self.configured?
|
16
|
-
CONFIG.all? {|field| field.present? }
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.perform_where_needed(data)
|
20
|
-
if delay > 0
|
21
|
-
perform_in(delay, data)
|
22
|
-
elsif async
|
23
|
-
perform_async(data)
|
24
|
-
else
|
25
|
-
new.perform(data)
|
26
|
-
end
|
27
|
-
source
|
28
|
-
end
|
29
|
-
|
30
|
-
def configured?
|
31
|
-
self.class.configured?
|
32
|
-
end
|
9
|
+
include Reactor::Workers::Configuration
|
33
10
|
|
34
11
|
def perform(data)
|
35
12
|
raise_unconfigured! unless configured?
|
@@ -59,22 +36,6 @@ module Reactor
|
|
59
36
|
msg.respond_to?(:deliver_now) || msg.respond_to?(:deliver)
|
60
37
|
end
|
61
38
|
|
62
|
-
def should_perform?
|
63
|
-
if Reactor.test_mode?
|
64
|
-
Reactor.test_mode_subscriber_enabled? source
|
65
|
-
else
|
66
|
-
true
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
private
|
71
|
-
|
72
|
-
def raise_unconfigured!
|
73
|
-
settings = Hash[CONFIG.map {|s| [s, self.class.send(s)] }]
|
74
|
-
raise UnconfiguredWorkerError.new(
|
75
|
-
"#{self.class.name} is not properly configured! Here are the settings: #{settings}"
|
76
|
-
)
|
77
|
-
end
|
78
39
|
end
|
79
40
|
end
|
80
41
|
end
|
data/lib/reactor.rb
CHANGED
@@ -15,10 +15,14 @@ class Auction < ActiveRecord::Base
|
|
15
15
|
event.actor.more_puppies! if event.name == 'another_event'
|
16
16
|
end
|
17
17
|
|
18
|
-
on_event :cat_delivered,
|
18
|
+
on_event :cat_delivered, async: false do |event|
|
19
19
|
puppies!
|
20
20
|
end
|
21
21
|
|
22
|
+
on_event :a_high_frequency_event, deprecated: true do |event|
|
23
|
+
raise 'hell'
|
24
|
+
end
|
25
|
+
|
22
26
|
def self.ring_bell(event)
|
23
27
|
"ring ring! #{event}"
|
24
28
|
end
|
@@ -123,19 +127,28 @@ describe Reactor::Subscribable do
|
|
123
127
|
expect { Reactor::Event.publish :auction }.not_to raise_error
|
124
128
|
end
|
125
129
|
|
126
|
-
describe '
|
127
|
-
it 'doesnt fire perform_async when
|
130
|
+
describe 'async flag' do
|
131
|
+
it 'doesnt fire perform_async when false' do
|
128
132
|
expect(Auction).to receive(:puppies!)
|
129
133
|
expect(Reactor::StaticSubscribers::Auction::CatDeliveredHandler).not_to receive(:perform_async)
|
130
134
|
Reactor::Event.publish(:cat_delivered)
|
131
135
|
end
|
132
136
|
|
133
|
-
it 'fires perform_async when
|
137
|
+
it 'fires perform_async when true / default' do
|
134
138
|
expect(Reactor::StaticSubscribers::Auction::WildcardHandler).to receive(:perform_async)
|
135
139
|
Reactor::Event.publish(:puppy_delivered)
|
136
140
|
end
|
137
141
|
end
|
138
142
|
|
143
|
+
describe 'deprecate flag for high-frequency events in production deployments' do
|
144
|
+
it 'doesnt enqueue subscriber worker when true' do
|
145
|
+
# so subscriber can be safely deleted in next deploy
|
146
|
+
expect {
|
147
|
+
Reactor::Event.publish(:a_high_frequency_event)
|
148
|
+
}.to_not raise_exception
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
139
152
|
describe '#perform' do
|
140
153
|
around(:each) do |example|
|
141
154
|
Reactor.in_test_mode { example.run }
|
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,47 @@
|
|
1
|
+
shared_examples_for 'configurable subscriber worker' do
|
2
|
+
describe '.configured?' do
|
3
|
+
context 'for unconfigured class' do
|
4
|
+
subject { FailingEventWorker.configured? }
|
5
|
+
|
6
|
+
it { is_expected.to eq(false) }
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'for configured class' do
|
10
|
+
subject { MyEventWorker.configured? }
|
11
|
+
|
12
|
+
it { is_expected.to eq(true) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '.perform_where_needed?' do
|
17
|
+
context 'for delayed worker' do
|
18
|
+
let(:klass) { MyDelayedWorker }
|
19
|
+
subject { klass.perform_where_needed(event_data) }
|
20
|
+
|
21
|
+
it 'uses perform_in to delay execution' do
|
22
|
+
expect(klass).to receive(:perform_in).with(1, event_data)
|
23
|
+
subject
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'for async workers' do
|
28
|
+
let(:klass) { MyEventWorker }
|
29
|
+
subject { klass.perform_where_needed(event_data) }
|
30
|
+
|
31
|
+
it 'uses perform_async to execute wherever' do
|
32
|
+
expect(klass).to receive(:perform_async).with(event_data)
|
33
|
+
subject
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'for immediate workers' do
|
38
|
+
let(:klass) { MyImmediateWorker }
|
39
|
+
subject { klass.perform_where_needed(event_data) }
|
40
|
+
|
41
|
+
it 'creates and executes new instance' do
|
42
|
+
expect_any_instance_of(klass).to receive(:perform).with(event_data)
|
43
|
+
subject
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -16,6 +16,7 @@ class MyEventWorker < Reactor::Workers::EventWorker
|
|
16
16
|
self.action = :fire_worker
|
17
17
|
self.async = true
|
18
18
|
self.delay = 0
|
19
|
+
self.deprecated = false
|
19
20
|
end
|
20
21
|
|
21
22
|
class MyBlockWorker < Reactor::Workers::EventWorker
|
@@ -23,6 +24,7 @@ class MyBlockWorker < Reactor::Workers::EventWorker
|
|
23
24
|
self.action = lambda { |event| :block_ran }
|
24
25
|
self.async = true
|
25
26
|
self.delay = 0
|
27
|
+
self.deprecated = false
|
26
28
|
end
|
27
29
|
|
28
30
|
class MyDelayedWorker < Reactor::Workers::EventWorker
|
@@ -30,6 +32,7 @@ class MyDelayedWorker < Reactor::Workers::EventWorker
|
|
30
32
|
self.action = :fire_worker
|
31
33
|
self.async = true
|
32
34
|
self.delay = 1 # seconds
|
35
|
+
self.deprecated = false
|
33
36
|
end
|
34
37
|
|
35
38
|
class MyImmediateWorker < Reactor::Workers::EventWorker
|
@@ -37,57 +40,14 @@ class MyImmediateWorker < Reactor::Workers::EventWorker
|
|
37
40
|
self.action = :fire_worker
|
38
41
|
self.async = false
|
39
42
|
self.delay = 0
|
43
|
+
self.deprecated = false
|
40
44
|
end
|
41
45
|
|
42
46
|
describe Reactor::Workers::EventWorker do
|
43
47
|
let(:event_name) { :fire_worker }
|
44
48
|
let(:event_data) { Hash[my_event_data: true] }
|
45
49
|
|
46
|
-
|
47
|
-
context 'for unconfigured class' do
|
48
|
-
subject { FailingEventWorker.configured? }
|
49
|
-
|
50
|
-
it { is_expected.to eq(false) }
|
51
|
-
end
|
52
|
-
|
53
|
-
context 'for configured class' do
|
54
|
-
subject { MyEventWorker.configured? }
|
55
|
-
|
56
|
-
it { is_expected.to eq(true) }
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
describe '.perform_where_needed?' do
|
61
|
-
context 'for delayed worker' do
|
62
|
-
let(:klass) { MyDelayedWorker }
|
63
|
-
subject { klass.perform_where_needed(event_data) }
|
64
|
-
|
65
|
-
it 'uses perform_in to delay execution' do
|
66
|
-
expect(klass).to receive(:perform_in).with(1, event_data)
|
67
|
-
subject
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
context 'for async workers' do
|
72
|
-
let(:klass) { MyEventWorker }
|
73
|
-
subject { klass.perform_where_needed(event_data) }
|
74
|
-
|
75
|
-
it 'uses perform_async to execute wherever' do
|
76
|
-
expect(klass).to receive(:perform_async).with(event_data)
|
77
|
-
subject
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
context 'for immediate workers' do
|
82
|
-
let(:klass) { MyImmediateWorker }
|
83
|
-
subject { klass.perform_where_needed(event_data) }
|
84
|
-
|
85
|
-
it 'creates and executes new instance' do
|
86
|
-
expect_any_instance_of(klass).to receive(:perform).with(event_data)
|
87
|
-
subject
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
50
|
+
it_behaves_like 'configurable subscriber worker'
|
91
51
|
|
92
52
|
describe '#perform' do
|
93
53
|
let(:klass) { MyEventWorker }
|
@@ -16,6 +16,7 @@ class MyMailerWorker < Reactor::Workers::MailerWorker
|
|
16
16
|
self.action = :fire_mailer
|
17
17
|
self.async = false
|
18
18
|
self.delay = 0
|
19
|
+
self.deprecated = false
|
19
20
|
end
|
20
21
|
|
21
22
|
class MyBlockMailerWorker < Reactor::Workers::MailerWorker
|
@@ -23,6 +24,7 @@ class MyBlockMailerWorker < Reactor::Workers::MailerWorker
|
|
23
24
|
self.async = false
|
24
25
|
self.delay = 0
|
25
26
|
self.action = lambda { |event| fire_mailer(event) }
|
27
|
+
self.deprecated = false
|
26
28
|
end
|
27
29
|
|
28
30
|
describe Reactor::Workers::MailerWorker do
|
@@ -34,6 +36,8 @@ describe Reactor::Workers::MailerWorker do
|
|
34
36
|
allow_any_instance_of(klass).to receive(:should_perform?).and_return(true)
|
35
37
|
end
|
36
38
|
|
39
|
+
it_behaves_like 'configurable subscriber worker'
|
40
|
+
|
37
41
|
it 'sends an email from symbol method name' do
|
38
42
|
expect { subject }.to change { ActionMailer::Base.deliveries.count }.by(1)
|
39
43
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reactor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.16.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- winfred
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2017-
|
14
|
+
date: 2017-08-18 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rails
|
@@ -228,6 +228,7 @@ files:
|
|
228
228
|
- lib/reactor/testing/matchers.rb
|
229
229
|
- lib/reactor/version.rb
|
230
230
|
- lib/reactor/workers.rb
|
231
|
+
- lib/reactor/workers/concerns/configuration.rb
|
231
232
|
- lib/reactor/workers/database_subscriber_worker.rb
|
232
233
|
- lib/reactor/workers/event_worker.rb
|
233
234
|
- lib/reactor/workers/mailer_worker.rb
|
@@ -241,6 +242,7 @@ files:
|
|
241
242
|
- spec/spec_helper.rb
|
242
243
|
- spec/subscription_spec.rb
|
243
244
|
- spec/support/active_record.rb
|
245
|
+
- spec/support/shared_examples.rb
|
244
246
|
- spec/workers/database_subscriber_worker_spec.rb
|
245
247
|
- spec/workers/event_worker_spec.rb
|
246
248
|
- spec/workers/mailer_worker_spec.rb
|
@@ -264,7 +266,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
264
266
|
version: '0'
|
265
267
|
requirements: []
|
266
268
|
rubyforge_project:
|
267
|
-
rubygems_version: 2.6.
|
269
|
+
rubygems_version: 2.6.12
|
268
270
|
signing_key:
|
269
271
|
specification_version: 4
|
270
272
|
summary: Sidekiq/ActiveRecord pubsub lib
|
@@ -278,6 +280,7 @@ test_files:
|
|
278
280
|
- spec/spec_helper.rb
|
279
281
|
- spec/subscription_spec.rb
|
280
282
|
- spec/support/active_record.rb
|
283
|
+
- spec/support/shared_examples.rb
|
281
284
|
- spec/workers/database_subscriber_worker_spec.rb
|
282
285
|
- spec/workers/event_worker_spec.rb
|
283
286
|
- spec/workers/mailer_worker_spec.rb
|