diff --git a/.rspec b/.rspec index c99d2e7..453fba1 100644 --- a/.rspec +++ b/.rspec @@ -1 +1,2 @@ --require spec_helper +--format doc diff --git a/.rubocop.yml b/.rubocop.yml index 5331cd0..ba8d7c0 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -54,6 +54,7 @@ Metrics/ClassLength: Metrics/MethodLength: Enabled: true + Max: 12 Exclude: - 'spec/**/*.rb' - 'test/**/*.rb' diff --git a/Gemfile.lock b/Gemfile.lock index be99ddb..dc3176e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -107,6 +107,7 @@ PLATFORMS arm64-darwin-20 arm64-darwin-21 arm64-darwin-22 + ruby x86_64-darwin-20 x86_64-darwin-21 x86_64-darwin-22 diff --git a/lib/ears.rb b/lib/ears.rb index 7c005a5..c9ece45 100644 --- a/lib/ears.rb +++ b/lib/ears.rb @@ -48,6 +48,11 @@ def setup(&block) Ears::Setup.new.instance_eval(&block) end + # Quick setup your consumers (including exchanges and queues). + def setup_consumers(*consumer_classes) + Ears::Setup.new.setup_consumers(*consumer_classes) + end + # Blocks the calling thread until +SIGTERM+ or +SIGINT+ is received. # Used to keep the process alive while processing messages. def run! diff --git a/lib/ears/setup.rb b/lib/ears/setup.rb index 6484d3c..d3622ed 100644 --- a/lib/ears/setup.rb +++ b/lib/ears/setup.rb @@ -58,6 +58,24 @@ def consumer(queue, consumer_class, threads = 1, args = {}) end end + # Sets up consumers, including bindings to exchanges and queues. + # + # @param [Array>] consumer_classes An array of subclasses of {Ears::Consumer} that call {Ears::Consumer#configure} in their class definition. + def setup_consumers(*consumer_classes) + consumer_classes.each do |consumer_class| + exchange = + exchange( + consumer_class.exchange, + consumer_class.exchange_type, + durable: consumer_class.durable_exchange, + ) + configured_queue = + queue(consumer_class.queue, consumer_class.queue_options) + configured_queue.bind(exchange, routing_key: consumer_class.routing_key) + consumer(configured_queue, consumer_class) + end + end + private def queue_options(bunny_opts, retry_arguments) diff --git a/spec/ears/consumer_spec.rb b/spec/ears/consumer_spec.rb index 1d3f0cb..2fd1dd5 100644 --- a/spec/ears/consumer_spec.rb +++ b/spec/ears/consumer_spec.rb @@ -57,7 +57,7 @@ it 'provides a default for durable_exchange' do custom_consumer_class.configure(mandatory_options) - expect(custom_consumer_class.durable_exchange).to eq(true) + expect(custom_consumer_class.durable_exchange).to be(true) end it 'provides a default for exchange_type' do @@ -119,7 +119,7 @@ mandatory_options.merge({ durable_exchange: false }), ) - expect(custom_consumer_class.durable_exchange).to eq(false) + expect(custom_consumer_class.durable_exchange).to be(false) end end diff --git a/spec/ears/setup_spec.rb b/spec/ears/setup_spec.rb index 747a579..a6d89bc 100644 --- a/spec/ears/setup_spec.rb +++ b/spec/ears/setup_spec.rb @@ -2,18 +2,25 @@ require 'ears/setup' RSpec.describe Ears::Setup do - let(:connection) { instance_double(Bunny::Session) } - let(:channel) { instance_double(Bunny::Channel, number: 1) } + subject(:setup) { Ears::Setup.new } + + let(:connection) { instance_double(Bunny::Session, create_channel: channel) } + let(:channel) do + instance_double( + Bunny::Channel, + number: 1, + prefetch: nil, + on_uncaught_exception: nil, + ) + end let(:exchange) { instance_double(Bunny::Exchange) } - let(:queue) { instance_double(Bunny::Queue, name: 'queue', options: {}) } + let(:queue) do + instance_double(Bunny::Queue, name: 'queue', channel: channel, options: {}) + end before do - allow(connection).to receive(:create_channel).and_return(channel) allow(Ears).to receive_messages(connection: connection, channel: channel) - allow(channel).to receive(:prefetch) - allow(channel).to receive(:on_uncaught_exception) allow(Bunny::Queue).to receive(:new).and_return(queue) - allow(queue).to receive(:channel).and_return(channel) end describe '#exchange' do @@ -25,7 +32,7 @@ {}, ).and_return(exchange) - expect(Ears::Setup.new.exchange('name', :topic)).to eq(exchange) + expect(setup.exchange('name', :topic)).to eq(exchange) end it 'passes the given options to the exchange' do @@ -36,9 +43,7 @@ { test: 1 }, ).and_return(exchange) - expect(Ears::Setup.new.exchange('name', :topic, { test: 1 })).to eq( - exchange, - ) + expect(setup.exchange('name', :topic, { test: 1 })).to eq(exchange) end end @@ -50,7 +55,7 @@ {}, ).and_return(queue) - expect(Ears::Setup.new.queue('name')).to eq(queue) + expect(setup.queue('name')).to eq(queue) end it 'passes the given options to the queue' do @@ -60,7 +65,7 @@ { test: 1 }, ).and_return(queue) - expect(Ears::Setup.new.queue('name', { test: 1 })).to eq(queue) + expect(setup.queue('name', { test: 1 })).to eq(queue) end context 'with retry queue' do @@ -78,7 +83,7 @@ ).and_return(queue) expect( - Ears::Setup.new.queue( + setup.queue( 'name', { retry_queue: true, retry_delay: 1000, test: 1 }, ), @@ -98,7 +103,7 @@ }, ) - expect(Ears::Setup.new.queue('name', retry_queue: true)).to eq(queue) + expect(setup.queue('name', retry_queue: true)).to eq(queue) end it 'adds the retry queue as a deadletter to the original queue' do @@ -113,7 +118,7 @@ }, ) - expect(Ears::Setup.new.queue('name', retry_queue: true)).to eq(queue) + expect(setup.queue('name', retry_queue: true)).to eq(queue) end it 'uses the given retry delay for the retry queue' do @@ -129,9 +134,9 @@ }, ) - expect( - Ears::Setup.new.queue('name', retry_queue: true, retry_delay: 1000), - ).to eq(queue) + expect(setup.queue('name', retry_queue: true, retry_delay: 1000)).to eq( + queue, + ) end it 'merges retry options into the arguments' do @@ -149,7 +154,7 @@ ).and_return(queue) expect( - Ears::Setup.new.queue( + setup.queue( 'name', { retry_queue: true, @@ -168,7 +173,7 @@ it 'creates an error queue with derived name if option is set' do expect(Bunny::Queue).to receive(:new).with(channel, 'name.error', {}) - expect(Ears::Setup.new.queue('name', error_queue: true)).to eq(queue) + expect(setup.queue('name', error_queue: true)).to eq(queue) end end end @@ -209,7 +214,7 @@ ) expect(queue).to receive(:subscribe_with).with(consumer_wrapper) - Ears::Setup.new.consumer(queue, MyConsumer) + setup.consumer(queue, MyConsumer) end it 'passes the consumer arguments' do @@ -232,7 +237,7 @@ ) expect(queue).to receive(:subscribe_with).with(consumer_wrapper) - Ears::Setup.new.consumer(queue, MyConsumer, 1, { a: 1 }) + setup.consumer(queue, MyConsumer, 1, { a: 1 }) end it 'creates a dedicated channel and queue for each consumer' do @@ -253,18 +258,13 @@ .exactly(3) .times - Ears::Setup.new.consumer(queue, MyConsumer, 3) + setup.consumer(queue, MyConsumer, 3) end it 'passes the prefetch argument to the channel' do expect(channel).to receive(:prefetch).with(5) - Ears::Setup.new.consumer( - queue, - MyConsumer, - 1, - { prefetch: 5, bla: 'test' }, - ) + setup.consumer(queue, MyConsumer, 1, { prefetch: 5, bla: 'test' }) end it 'numbers the consumers' do @@ -283,7 +283,7 @@ {}, ).and_return(consumer_wrapper) - Ears::Setup.new.consumer(queue, MyConsumer, 2) + setup.consumer(queue, MyConsumer, 2) end end end diff --git a/spec/ears_spec.rb b/spec/ears_spec.rb index daddda8..55a2482 100644 --- a/spec/ears_spec.rb +++ b/spec/ears_spec.rb @@ -133,6 +133,17 @@ end end + describe '.setup_consumers' do + let(:klasses) { %i[first_class second_class] } + + it 'calls the setup_consumers' do + setup = instance_double(Ears::Setup, setup_consumers: nil) + allow(Ears::Setup).to receive(:new).and_return(setup) + described_class.setup_consumers(*klasses) + expect(setup).to have_received(:setup_consumers).with(*klasses) + end + end + describe '.stop!' do let(:bunny_session) do instance_double(