chasqui 1.0.0.pre.rc1 → 1.0.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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +90 -78
  4. data/chasqui.gemspec +1 -1
  5. data/lib/chasqui.rb +83 -5
  6. data/lib/chasqui/cli.rb +8 -2
  7. data/lib/chasqui/config.rb +70 -8
  8. data/lib/chasqui/{queue_adapters → queue_adapter}/redis_queue_adapter.rb +6 -12
  9. data/lib/chasqui/subscriber.rb +1 -78
  10. data/lib/chasqui/subscription_builder.rb +135 -0
  11. data/lib/chasqui/subscription_builder/resque_subscription_builder.rb +31 -0
  12. data/lib/chasqui/subscription_builder/sidekiq_subscription_builder.rb +27 -0
  13. data/lib/chasqui/subscriptions.rb +20 -38
  14. data/lib/chasqui/version.rb +1 -1
  15. data/spec/integration/pubsub_examples.rb +1 -0
  16. data/spec/integration/setup/resque.rb +38 -1
  17. data/spec/integration/setup/sidekiq.rb +40 -1
  18. data/spec/lib/chasqui/brokers/redis_broker_spec.rb +12 -10
  19. data/spec/lib/chasqui/config_spec.rb +3 -3
  20. data/spec/lib/chasqui/{queue_adapters → queue_adapter}/redis_queue_adapter_spec.rb +6 -19
  21. data/spec/lib/chasqui/subscriber_spec.rb +4 -114
  22. data/spec/lib/chasqui/subscription_builder/resque_subscription_builder_spec.rb +20 -0
  23. data/spec/lib/chasqui/subscription_builder/sidekiq_subscription_builder_spec.rb +27 -0
  24. data/spec/lib/chasqui/subscription_builder_spec.rb +41 -0
  25. data/spec/lib/chasqui/subscriptions_spec.rb +4 -4
  26. data/spec/lib/chasqui_spec.rb +76 -12
  27. data/spec/support/chasqui_spec_helpers.rb +0 -21
  28. data/spec/support/fake_worker.rb +2 -0
  29. data/spec/support/shared_examples/subscription_builder_examples.rb +99 -0
  30. metadata +22 -16
  31. data/lib/chasqui/worker.rb +0 -81
  32. data/spec/integration/setup/subscribers.rb +0 -30
  33. data/spec/lib/chasqui/worker_spec.rb +0 -96
  34. data/spec/support/fake_subscriber.rb +0 -13
@@ -5,4 +5,43 @@ Sidekiq.configure_server do |config|
5
5
  config.redis = { url: ENV['REDIS_URL'], namespace: ENV['REDIS_NAMESPACE'] }
6
6
  end
7
7
 
8
- require './spec/integration/setup/subscribers'
8
+ def log_event(worker, event, *args)
9
+ queue = worker.sidekiq_options_hash['queue']
10
+ event['worker_args'] = args
11
+
12
+ Chasqui.redis.rpush "#{queue}:event_log", event.to_json
13
+ end
14
+
15
+ class UserSignupWorker
16
+ include Sidekiq::Worker
17
+ sidekiq_options queue: 'app1'
18
+
19
+ def perform(event, *args)
20
+ log_event self, event, *args
21
+ end
22
+ end
23
+
24
+ class TransactionWorker
25
+ include Sidekiq::Worker
26
+ sidekiq_options queue: 'app2'
27
+
28
+ def perform(event, *args)
29
+ log_event self, event, *args
30
+ end
31
+ end
32
+
33
+ class UserCancelWorker
34
+ include Sidekiq::Worker
35
+ sidekiq_options queue: 'app2'
36
+
37
+ def perform(event, *args)
38
+ log_event self, event, *args
39
+ end
40
+ end
41
+
42
+ Chasqui.subscribe do
43
+ on 'user.signup', UserSignupWorker
44
+ on 'user.cancel', UserCancelWorker
45
+ on 'account.credit', TransactionWorker
46
+ on 'account.debit', TransactionWorker
47
+ end
@@ -1,18 +1,18 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  class Worker1
4
- include Chasqui::Subscriber
5
- subscribe channel: 'app1', queue: 'queue1'
4
+ @queue = 'queue1'
5
+ def self.perform(event, *args); end
6
6
  end
7
7
 
8
8
  class Worker2
9
- include Chasqui::Subscriber
10
- subscribe channel: 'app2', queue: 'queue2'
9
+ @queue = 'queue2'
10
+ def self.perform(event, *args); end
11
11
  end
12
12
 
13
13
  class Worker3
14
- include Chasqui::Subscriber
15
- subscribe channel: 'app1', queue: 'queue3'
14
+ @queue = 'queue3'
15
+ def self.perform(event, *args); end
16
16
  end
17
17
 
18
18
  describe Chasqui::RedisBroker do
@@ -27,9 +27,11 @@ describe Chasqui::RedisBroker do
27
27
 
28
28
  describe '#forward_event' do
29
29
  before do
30
- Chasqui.register Worker1
31
- Chasqui.register Worker2
32
- Chasqui.register Worker3
30
+ Chasqui.subscribe do
31
+ on 'app1', Worker1
32
+ on 'app2', Worker2
33
+ on 'app1', Worker3
34
+ end
33
35
  end
34
36
 
35
37
  it 'places the event on all subscriber queues' do
@@ -65,7 +67,7 @@ describe Chasqui::RedisBroker do
65
67
  Chasqui.publish 'app2', foo: 'bar'
66
68
 
67
69
  job = JSON.parse nnredis.blpop('queue:queue2')[1]
68
- expect(job).to include('class' => 'Chasqui::Workers::Worker2')
70
+ expect(job).to include('class' => 'Worker2')
69
71
 
70
72
  event = {
71
73
  'channel' => 'app2',
@@ -4,11 +4,11 @@ describe Chasqui::Config do
4
4
 
5
5
  context 'defaults' do
6
6
  it { expect(subject.channel_prefix).to be nil }
7
- it { expect(subject.default_queue).to eq('chasqui-subscribers') }
8
- it { expect(subject.inbox_queue).to eq('inbox') }
7
+ it { expect(subject.default_queue).to eq('chasqui-workers') }
8
+ it { expect(subject.inbox_queue).to eq('chasqui-inbox') }
9
9
  it { expect(subject.redis.client.db).to eq(0) }
10
10
  it { expect(subject.broker_poll_interval).to eq(3) }
11
- it { expect(subject.queue_adapter).to eq(Chasqui::QueueAdapters::RedisQueueAdapter) }
11
+ it { expect(subject.queue_adapter).to eq(Chasqui::QueueAdapter::RedisQueueAdapter) }
12
12
 
13
13
  it do
14
14
  # remove chasqui's test environment logger
@@ -1,14 +1,11 @@
1
1
  require 'spec_helper'
2
2
 
3
- class MySubscriber
4
- include Chasqui::Subscriber
5
- subscribe channel: 'channel-name', queue: 'queue-name'
6
- end
7
-
8
- describe Chasqui::QueueAdapters::RedisQueueAdapter do
3
+ describe Chasqui::QueueAdapter::RedisQueueAdapter do
9
4
  it_behaves_like 'a queue adapter'
10
5
 
11
- let(:subscriber) { MySubscriber }
6
+ let(:subscriber) do
7
+ Chasqui::Subscriber.new('channel-name', 'queue-name', FakeWorker)
8
+ end
12
9
 
13
10
  describe '#bind / #unbind' do
14
11
  let(:key) { 'subscriptions:channel-name' }
@@ -26,12 +23,7 @@ describe Chasqui::QueueAdapters::RedisQueueAdapter do
26
23
  it 'persists the subscriptions to redis' do
27
24
  subject.bind(subscriber)
28
25
  subscriptions = redis.smembers(key)
29
- expect(subscriptions).to eq(
30
- ['resque/Chasqui::Workers::MySubscriber/resque:queue:queue-name'])
31
-
32
- expect(Chasqui::Workers.constants).to include(:MySubscriber)
33
- worker = Chasqui::Workers.const_get :MySubscriber
34
- expect(worker.subscriber).to eq(MySubscriber)
26
+ expect(subscriptions).to eq(['resque/FakeWorker/resque:queue:queue-name'])
35
27
 
36
28
  redis.sadd key, 'random'
37
29
 
@@ -52,14 +44,9 @@ describe Chasqui::QueueAdapters::RedisQueueAdapter do
52
44
  it 'persists the subscription to redis' do
53
45
  subject.bind(subscriber)
54
46
  subscriptions = redis.smembers('subscriptions:channel-name')
55
- expect(subscriptions).to eq(
56
- ['sidekiq/Chasqui::Workers::MySubscriber/queue:queue-name'])
47
+ expect(subscriptions).to eq(['sidekiq/FakeWorker/queue:queue-name'])
57
48
  expect(redis_no_namespace.smembers 'queues').to eq(['queue-name'])
58
49
 
59
- expect(Chasqui::Workers.constants).to include(:MySubscriber)
60
- worker = Chasqui::Workers.const_get :MySubscriber
61
- expect(worker.subscriber).to eq(MySubscriber)
62
-
63
50
  redis.sadd key, 'random'
64
51
 
65
52
  subject.unbind(subscriber)
@@ -1,119 +1,9 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Chasqui::Subscriber do
4
- let(:subscriber_class) { Class.new { include Chasqui::Subscriber } }
5
- let(:redis) { Redis.new }
4
+ subject { Chasqui::Subscriber.new 'foo', 'bar', 'worker' }
6
5
 
7
- describe '#event' do
8
- it { expect(subscriber_class.new(event: 'foo').event).to eq('foo') }
9
- it { expect(-> { subscriber_class.new }).to raise_error(KeyError) }
10
- end
11
-
12
- describe '#redis' do
13
- let(:subscriber) { subscriber_class.new event: nil }
14
-
15
- context 'default' do
16
- it { expect(subscriber.redis).to eq(Chasqui.redis) }
17
- end
18
-
19
- context 'custom' do
20
- let(:redis) { Redis.new }
21
- let(:subscriber) { subscriber_class.new event: nil, redis: redis }
22
- it { expect(subscriber.redis.object_id).to eq(redis.object_id) }
23
- end
24
- end
25
-
26
- describe '#logger' do
27
- let(:subscriber) { subscriber_class.new event: nil }
28
-
29
- context 'default' do
30
- it { expect(subscriber.logger).to eq(Chasqui.logger) }
31
- end
32
-
33
- context 'custom' do
34
- let(:logger) { FakeLogger.new }
35
- let(:subscriber) { subscriber_class.new event: nil, logger: logger }
36
- it { expect(subscriber.logger).to eq(logger) }
37
- end
38
- end
39
-
40
- describe '.channels' do
41
- before do
42
- expect(Chasqui).to receive(:register).with(subscriber_class).at_least(:once)
43
- subscriber_class.subscribe channel: 'some.channel'
44
- end
45
-
46
- context 'default' do
47
- it { expect(subscriber_class.channels).to include('some.channel') }
48
- end
49
-
50
- context 'with default prefix' do
51
- before do
52
- Chasqui.config.channel_prefix = 'prefix'
53
- subscriber_class.subscribe channel: 'some.channel'
54
- end
55
-
56
- it { expect(subscriber_class.channels).to include('prefix.some.channel') }
57
- end
58
-
59
- context 'with custom prefix' do
60
- before { Chasqui.config.channel_prefix = 'prefix' }
61
-
62
- it 'uses the custom prefix' do
63
- subscriber_class.subscribe channel: 'another.channel', prefix: 'custom'
64
- expect(subscriber_class.channels).to include('custom.another.channel')
65
- end
66
-
67
- it 'removes the prefix' do
68
- subscriber_class.subscribe channel: 'another.channel', prefix: nil
69
- expect(subscriber_class.channels).to include('another.channel')
70
- end
71
- end
72
-
73
- context 'multiple channels' do
74
- before { subscriber_class.subscribe channel: ['foo', 'bar'], prefix: 'custom' }
75
-
76
- it 'subscribes to multiple channels' do
77
- expect(subscriber_class.channels).to eq(['custom.foo', 'custom.bar'])
78
- end
79
- end
80
- end
81
-
82
- describe '.queue' do
83
- context 'default' do
84
- it { expect(subscriber_class.queue).to eq(Chasqui.default_queue) }
85
- end
86
-
87
- context 'custom' do
88
- before do
89
- expect(Chasqui).to receive(:register).with(subscriber_class)
90
- subscriber_class.subscribe queue: 'custom-queue'
91
- end
92
-
93
- it { expect(subscriber_class.queue).to eq('custom-queue') }
94
- end
95
- end
96
-
97
- describe '#perform' do
98
- let(:subscriber) { subscriber_class.new event: nil }
99
-
100
- it 'is not implemented' do
101
- expect(-> {
102
- subscriber.perform foo: 'bar'
103
- }).to raise_error(NotImplementedError)
104
- end
105
- end
106
-
107
- describe '.inherited' do
108
- before { allow(Chasqui).to receive(:register) }
109
-
110
- it 'maintains a registry of inherited classes' do
111
- klass = Class.new do
112
- include Chasqui::Subscriber
113
- subscribe
114
- end
115
-
116
- expect(Chasqui::Subscriber.subscribers).to include(klass)
117
- end
118
- end
6
+ it { expect(subject.channel).to eq('foo') }
7
+ it { expect(subject.queue).to eq('bar') }
8
+ it { expect(subject.worker).to eq('worker') }
119
9
  end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chasqui::ResqueSubscriptionBuilder do
4
+ resque_worker = Class.new do
5
+ @queue = 'pubsub'
6
+
7
+ def self.perform(event, *args)
8
+ end
9
+ end
10
+
11
+ it_behaves_like 'a subscription builder', resque_worker
12
+
13
+ def queue_name(worker)
14
+ worker.instance_variable_get(:@queue)
15
+ end
16
+
17
+ def perform(worker, *perform_args)
18
+ worker.perform *perform_args
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ if sidekiq_supported_ruby_version?
4
+ describe Chasqui::SidekiqSubscriptionBuilder do
5
+ sidekiq_worker = Class.new do
6
+ include Sidekiq::Worker
7
+ sidekiq_options queue: 'pubsub'
8
+
9
+ def perform(event, *args)
10
+ end
11
+ end
12
+
13
+ it_behaves_like 'a subscription builder', sidekiq_worker
14
+
15
+ def queue_name(worker)
16
+ worker.sidekiq_options['queue']
17
+ end
18
+
19
+ def perform(worker, *perform_args)
20
+ worker.new.perform *perform_args
21
+ end
22
+
23
+ def expect_worker_to_support_backend(worker)
24
+ expect(worker.included_modules).to include(Sidekiq::Worker)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chasqui::SubscriptionBuilder do
4
+ describe '.builder' do
5
+ let(:subscriptions) { double }
6
+ let(:options) { { foo: 'bar' } }
7
+
8
+ before { reset_config }
9
+
10
+ context 'resque' do
11
+ it do
12
+ Chasqui.config.worker_backend = :resque
13
+
14
+ builder = described_class.builder subscriptions, options
15
+ expect(builder.subscriptions).to eq(subscriptions)
16
+ expect(builder.default_options).to eq(options)
17
+ expect(builder).to be_instance_of(Chasqui::ResqueSubscriptionBuilder)
18
+ end
19
+ end
20
+
21
+ context 'sidekiq' do
22
+ it do
23
+ Chasqui.config.worker_backend = :sidekiq
24
+
25
+ builder = described_class.builder subscriptions, options
26
+ expect(builder.subscriptions).to eq(subscriptions)
27
+ expect(builder.default_options).to eq(options)
28
+ expect(builder).to be_instance_of(Chasqui::SidekiqSubscriptionBuilder)
29
+ end
30
+ end
31
+
32
+ context 'neither' do
33
+ it do
34
+ Chasqui.config.worker_backend = :unknown
35
+ expect(-> {
36
+ described_class.builder subscriptions, options
37
+ }).to raise_error(Chasqui::ConfigurationError)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -5,10 +5,10 @@ describe Chasqui::Subscriptions do
5
5
 
6
6
  subject { Chasqui::Subscriptions.new queue_adapter }
7
7
 
8
- let(:a) { new_subscriber 'SubscriberA', channel: 'ch1', queue: 'foo' }
9
- let(:b) { new_subscriber 'SubscriberB', channel: 'ch1', queue: 'bar' }
10
- let(:c) { new_subscriber 'SubscriberC', channel: 'ch2', queue: 'foo' }
11
- let(:d) { new_subscriber 'SubscriberD', channel: 'ch1', queue: 'foo' }
8
+ let(:a) { Chasqui::Subscriber.new 'ch1', 'foo', double('workerA') }
9
+ let(:b) { Chasqui::Subscriber.new 'ch1', 'bar', double('workerB') }
10
+ let(:c) { Chasqui::Subscriber.new 'ch2', 'foo', double('workerC') }
11
+ let(:d) { Chasqui::Subscriber.new 'ch1', 'foo', double('workerD') }
12
12
 
13
13
  before do
14
14
  reset_config
@@ -31,16 +31,7 @@ describe Chasqui do
31
31
  subject { Chasqui.subscriptions }
32
32
 
33
33
  it { should be_instance_of(Chasqui::Subscriptions) }
34
- it { expect(subject.queue_adapter).to be_instance_of(Chasqui::QueueAdapters::RedisQueueAdapter) }
35
- end
36
-
37
- describe 'subscription management delegates' do
38
- [:register, :unregister].each do |m|
39
- it "delegates :#{m} to #subscriptions" do
40
- expect(Chasqui.subscriptions).to receive(m)
41
- Chasqui.send m
42
- end
43
- end
34
+ it { expect(subject.queue_adapter).to be_instance_of(Chasqui::QueueAdapter::RedisQueueAdapter) }
44
35
  end
45
36
 
46
37
  describe '.publish' do
@@ -57,7 +48,7 @@ describe Chasqui do
57
48
  end
58
49
 
59
50
  payloads.each do |payload|
60
- event = JSON.load Chasqui.redis.rpop('inbox')
51
+ event = JSON.load Chasqui.redis.rpop(Chasqui.config.inbox_queue)
61
52
  expect(event['channel']).to eq('test.event')
62
53
  expect(event['payload']).to eq(payload)
63
54
  expect(event['created_at'].to_f).to be_within(0.1).of(Time.now.to_f)
@@ -67,8 +58,81 @@ describe Chasqui do
67
58
 
68
59
  it 'supports retries' do
69
60
  Chasqui.publish 'test.event', :foo, :bar, foo: 'bar', job_options: { retry: false }
70
- event = JSON.load Chasqui.redis.rpop('inbox')
61
+ event = JSON.load Chasqui.redis.rpop(Chasqui.config.inbox_queue)
71
62
  expect(event['retry']).to eq(false)
72
63
  end
73
64
  end
65
+
66
+ describe '.subscribe' do
67
+ let(:fake_builder) do
68
+ Class.new(Chasqui::SubscriptionBuilder) do
69
+ def self.handlers
70
+ @handlers ||= []
71
+ end
72
+
73
+ def on(channel, worker, options={})
74
+ self.class.handlers << [channel, worker, options]
75
+ end
76
+ end
77
+ end
78
+
79
+ before { reset_config }
80
+
81
+ it 'evaluates the block with a subscription builder binding' do
82
+ results = {}
83
+ worker = Class.new
84
+ allow(Chasqui::SubscriptionBuilder).to receive(:builder)
85
+ .and_return(fake_builder.new(nil))
86
+
87
+ Chasqui.subscribe queue: 'foo' do
88
+ results[:builder] = self
89
+ on 'channel', worker
90
+ end
91
+
92
+ builder = results[:builder]
93
+ expect(fake_builder.handlers.first).to eq(['channel', worker, {}])
94
+ end
95
+ end
96
+
97
+ FakeWorker1 = Class.new
98
+ FakeWorker2 = Class.new
99
+
100
+ describe '.unsubscribe' do
101
+ let(:subscriptions) { Chasqui.subscriptions }
102
+
103
+ before do
104
+ reset_chasqui
105
+ Chasqui.config.queue_adapter = FakeQueueAdapter
106
+ allow_any_instance_of(FakeQueueAdapter).to receive(:bind)
107
+ end
108
+
109
+ it 'unsubscribes all workers' do
110
+ sub1 = Chasqui::Subscriber.new 'channel', 'queue', FakeWorker1
111
+ sub2 = Chasqui::Subscriber.new 'channel', 'queue', FakeWorker2
112
+
113
+ subscriptions.register sub1
114
+ subscriptions.register sub2
115
+ expect(subscriptions.find 'channel', 'queue').to eq([sub1, sub2])
116
+
117
+ expect_any_instance_of(FakeQueueAdapter).to receive(:unbind).with(sub1)
118
+ expect_any_instance_of(FakeQueueAdapter).to receive(:unbind).with(sub2)
119
+
120
+ Chasqui.unsubscribe('channel', 'queue')
121
+ expect(subscriptions.find 'channel', 'queue').to be_empty
122
+ end
123
+
124
+ it 'unsubscribes a single worker' do
125
+ sub1 = Chasqui::Subscriber.new 'channel', 'queue', FakeWorker1
126
+ sub2 = Chasqui::Subscriber.new 'channel', 'queue', FakeWorker2
127
+
128
+ subscriptions.register sub1
129
+ subscriptions.register sub2
130
+ expect(subscriptions.find 'channel', 'queue').to eq([sub1, sub2])
131
+
132
+ expect_any_instance_of(FakeQueueAdapter).to receive(:unbind).with(sub1)
133
+
134
+ Chasqui.unsubscribe('channel', 'queue', FakeWorker1)
135
+ expect(subscriptions.find 'channel', 'queue').to eq([sub2])
136
+ end
137
+ end
74
138
  end