diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4b82b1b..4cbd8f7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,24 +24,26 @@ jobs: - '2.6' - '2.7' - '3.0' + - '3.1' + - '3.2' gemfile: - - gemfiles/activejob_4.2.x.gemfile - - gemfiles/activejob_5.2.x.gemfile - gemfiles/activejob_6.0.x.gemfile - gemfiles/activejob_6.1.x.gemfile - gemfiles/activejob_7.0.x.gemfile + - gemfiles/activejob_7.1.x.gemfile exclude: - - ruby: '2.7' - gemfile: gemfiles/activejob_4.2.x.gemfile - - ruby: '3.0' - gemfile: gemfiles/activejob_4.2.x.gemfile - - ruby: '3.0' - gemfile: gemfiles/activejob_5.2.x.gemfile - ruby: '2.5' gemfile: gemfiles/activejob_7.0.x.gemfile - ruby: '2.6' gemfile: gemfiles/activejob_7.0.x.gemfile - + - ruby: '2.5' + gemfile: gemfiles/activejob_7.1.x.gemfile + - ruby: '2.6' + gemfile: gemfiles/activejob_7.1.x.gemfile + - ruby: '3.2' + gemfile: gemfiles/activejob_6.0.x.gemfile + - ruby: '3.2' + gemfile: gemfiles/activejob_6.1.x.gemfile steps: - name: Checkout code uses: actions/checkout@v2 diff --git a/Appraisals b/Appraisals index 7987d85..4a62664 100644 --- a/Appraisals +++ b/Appraisals @@ -1,21 +1,20 @@ # frozen_string_literal: true -appraise 'activejob-4.2.x' do - gem 'rails', '~> 4.2.11' -end - -appraise 'activejob-5.2.x' do - gem 'rails', '~> 5.2.4' -end - appraise 'activejob-6.0.x' do gem 'rails', '~> 6.0.3' + gem 'concurrent-ruby', '1.3.4' end appraise 'activejob-6.1.x' do gem 'rails', '~> 6.1.0' + gem 'concurrent-ruby', '1.3.4' end appraise 'activejob-7.0.x' do gem 'rails', '~> 7.0.0' + gem 'concurrent-ruby', '1.3.4' +end + +appraise 'activejob-7.1.x' do + gem 'rails', '~> 7.1.0' end diff --git a/advanced-sneakers-activejob.gemspec b/advanced-sneakers-activejob.gemspec index eebc873..fd171f7 100644 --- a/advanced-sneakers-activejob.gemspec +++ b/advanced-sneakers-activejob.gemspec @@ -31,7 +31,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'bunny-publisher', '~> 0.2.0' spec.add_dependency 'sneakers', '~> 2.7' - spec.add_development_dependency 'appraisal', '~> 2.3.0' + spec.add_development_dependency 'appraisal', '~> 2.5.0' spec.add_development_dependency 'bundler' spec.add_development_dependency 'pry-byebug' spec.add_development_dependency 'rabbitmq_http_api_client', '~> 1.13' diff --git a/gemfiles/activejob_6.0.x.gemfile b/gemfiles/activejob_6.0.x.gemfile index 2e0ebbc..109780e 100644 --- a/gemfiles/activejob_6.0.x.gemfile +++ b/gemfiles/activejob_6.0.x.gemfile @@ -3,5 +3,6 @@ source "https://rubygems.org" gem "rails", "~> 6.0.3" +gem "concurrent-ruby", "1.3.4" gemspec path: "../" diff --git a/gemfiles/activejob_6.1.x.gemfile b/gemfiles/activejob_6.1.x.gemfile index dd95a47..bf4ad3b 100644 --- a/gemfiles/activejob_6.1.x.gemfile +++ b/gemfiles/activejob_6.1.x.gemfile @@ -3,5 +3,6 @@ source "https://rubygems.org" gem "rails", "~> 6.1.0" +gem "concurrent-ruby", "1.3.4" gemspec path: "../" diff --git a/gemfiles/activejob_7.0.x.gemfile b/gemfiles/activejob_7.0.x.gemfile index 9af0ae3..04654c1 100644 --- a/gemfiles/activejob_7.0.x.gemfile +++ b/gemfiles/activejob_7.0.x.gemfile @@ -3,5 +3,6 @@ source "https://rubygems.org" gem "rails", "~> 7.0.0" +gem "concurrent-ruby", "1.3.4" gemspec path: "../" diff --git a/gemfiles/activejob_7.1.x.gemfile b/gemfiles/activejob_7.1.x.gemfile new file mode 100644 index 0000000..35a0ba3 --- /dev/null +++ b/gemfiles/activejob_7.1.x.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rails", "~> 7.1.0" + +gemspec path: "../" diff --git a/lib/advanced_sneakers_activejob/workers_registry.rb b/lib/advanced_sneakers_activejob/workers_registry.rb index 44060cc..72a1baa 100644 --- a/lib/advanced_sneakers_activejob/workers_registry.rb +++ b/lib/advanced_sneakers_activejob/workers_registry.rb @@ -70,15 +70,41 @@ def define_active_job_consumers active_job_classes_with_matching_adapter.each do |worker| AdvancedSneakersActiveJob.define_consumer(queue_name: worker.new.queue_name) end + + action_mailer_classes_with_matching_adapter.each do |mailer| + AdvancedSneakersActiveJob.define_consumer(queue_name: mailer_queue_name(mailer)) + end end private def active_job_classes_with_matching_adapter ([ActiveJob::Base] + ActiveJob::Base.descendants).select do |klass| - klass.queue_adapter == ::ActiveJob::QueueAdapters::AdvancedSneakersAdapter || - klass.queue_adapter.is_a?(::ActiveJob::QueueAdapters::AdvancedSneakersAdapter) + advanced_sneakers_adapter?(klass) && + !(defined?(ActionMailer::Base) && klass == ActionMailer::MailDeliveryJob) + end + end + + def action_mailer_classes_with_matching_adapter + return [] if !defined?(ActionMailer::Base) || + ActionMailer.gem_version < Gem::Version.new('6.0.0') || + !advanced_sneakers_adapter?(ActionMailer::MailDeliveryJob) + + ([ActionMailer::Base] + ActionMailer::Base.descendants).select do |klass| + klass.delivery_job == ActionMailer::MailDeliveryJob end end + + def advanced_sneakers_adapter?(klass) + klass.queue_adapter == ::ActiveJob::QueueAdapters::AdvancedSneakersAdapter || + klass.queue_adapter.is_a?(::ActiveJob::QueueAdapters::AdvancedSneakersAdapter) + end + + # Gets the queue name for the mailer with activejob prefix and delimiter + # Queue name from ActionMailer::Base.deliver_later_queue_name is not prefixed + # and has no delimiter, so we need to add them manually + def mailer_queue_name(mailer) + [ActiveJob::Base.queue_name_prefix, mailer.deliver_later_queue_name.to_s].compact.join(ActiveJob::Base.queue_name_delimiter) + end end end diff --git a/spec/integration/consumers_spec.rb b/spec/integration/consumers_spec.rb index e5a49b3..67e0ba8 100644 --- a/spec/integration/consumers_spec.rb +++ b/spec/integration/consumers_spec.rb @@ -78,14 +78,90 @@ class DynamicQueueJob < ApplicationJob end end - if ActiveJob.gem_version >= Gem::Version.new('5.0') - context 'when there are ActiveJob classes with custom queue adapter' do + context 'when there are ActiveJob classes with custom queue adapter' do + subject do + in_app_process(adapter: :advanced_sneakers) do + class FooJob < ApplicationJob + self.queue_adapter = :async + + queue_as :bar + end + + AdvancedSneakersActiveJob.configure { |c| c.activejob_workers_strategy = :only } + + Sneakers::Worker::Classes.call.map { |consumer| [consumer.name, consumer.queue_name] }.to_h + end + end + + it 'are defined for queue from matching adapter only' do + expected_consumers = { + 'AdvancedSneakersActiveJob::DefaultConsumer' => 'default', # default consumer + 'AdvancedSneakersActiveJob::MailersConsumer' => 'mailers', # action mailer consumer + 'AdvancedSneakersActiveJob::CustomConsumer' => 'custom' # see CustomQueueJob in spec/apps/app/jobs + } + + expect(subject.first).to eq(expected_consumers) + end + end + + context 'when advanced_sneakers is set as custom adapter' do + subject do + in_app_process(adapter: :inline) do + class FooJob < ApplicationJob + self.queue_adapter = :advanced_sneakers + + queue_as :bar + end + + AdvancedSneakersActiveJob.configure { |c| c.activejob_workers_strategy = :only } + + Sneakers::Worker::Classes.call.map { |consumer| [consumer.name, consumer.queue_name] }.to_h + end + end + + it 'are defined for queue from matching adapter only' do + expected_consumers = { + 'AdvancedSneakersActiveJob::BarConsumer' => 'bar' # bar queue consumer for FooJob + } + + expect(subject.first).to eq(expected_consumers) + end + end + + context 'when ActionMailer::Base has deliver_later_queue_name globally defined' do + subject do + in_app_process(adapter: :advanced_sneakers) do + ActionMailer::Base.deliver_later_queue_name = 'bar' + + AdvancedSneakersActiveJob.configure { |c| c.activejob_workers_strategy = :only } + + Sneakers::Worker::Classes.call.map { |consumer| [consumer.name, consumer.queue_name] }.to_h + end + end + + it 'are defined for queue from matching adapter only' do + expected_consumers = { + 'AdvancedSneakersActiveJob::BarConsumer' => 'bar', # bar queue consumer for FooMailer + 'AdvancedSneakersActiveJob::DefaultConsumer' => 'default', # default consumer + 'AdvancedSneakersActiveJob::CustomConsumer' => 'custom', # see CustomQueueJob in spec/apps/app/jobs + } + + expect(subject.first).to eq(expected_consumers) + end + end + + # Support for mailer specific queue name was added in Rails 7.1 + # https://github.com/rails/rails/pull/47408 + if ActiveJob.gem_version >= Gem::Version.new('7.1') + context 'when there are ActionMailer classes with queue defined' do subject do in_app_process(adapter: :advanced_sneakers) do - class FooJob < ApplicationJob - self.queue_adapter = :async + class FooMailer < ActionMailer::Base + self.deliver_later_queue_name = 'bar' + end - queue_as :bar + class BarMailer < ActionMailer::Base + self.deliver_later_queue_name = 'baz' end AdvancedSneakersActiveJob.configure { |c| c.activejob_workers_strategy = :only } @@ -96,24 +172,35 @@ class FooJob < ApplicationJob it 'are defined for queue from matching adapter only' do expected_consumers = { + 'AdvancedSneakersActiveJob::BarConsumer' => 'bar', # bar queue consumer for FooMailer + 'AdvancedSneakersActiveJob::BazConsumer' => 'baz', # baz queue consumer for BarMailer 'AdvancedSneakersActiveJob::DefaultConsumer' => 'default', # default consumer 'AdvancedSneakersActiveJob::MailersConsumer' => 'mailers', # action mailer consumer - 'AdvancedSneakersActiveJob::CustomConsumer' => 'custom' # see CustomQueueJob in spec/apps/app/jobs + 'AdvancedSneakersActiveJob::CustomConsumer' => 'custom', # see CustomQueueJob in spec/apps/app/jobs } expect(subject.first).to eq(expected_consumers) end end - context 'when advanced_sneakers is set as custom adapter' do + context 'when there are ActionMailer classes with custom delivery jobs' do subject do - in_app_process(adapter: :inline) do - class FooJob < ApplicationJob - self.queue_adapter = :advanced_sneakers + in_app_process(adapter: :advanced_sneakers) do + class CustomDeliveryJob < ActionMailer::MailDeliveryJob + self.queue_adapter = :async queue_as :bar end + class FooMailer < ActionMailer::Base + self.delivery_job = CustomDeliveryJob + self.deliver_later_queue_name = 'bar' + end + + class BarMailer < ActionMailer::Base + self.deliver_later_queue_name = 'baz' + end + AdvancedSneakersActiveJob.configure { |c| c.activejob_workers_strategy = :only } Sneakers::Worker::Classes.call.map { |consumer| [consumer.name, consumer.queue_name] }.to_h @@ -122,7 +209,10 @@ class FooJob < ApplicationJob it 'are defined for queue from matching adapter only' do expected_consumers = { - 'AdvancedSneakersActiveJob::BarConsumer' => 'bar' # bar queue consumer for FooJob + 'AdvancedSneakersActiveJob::BazConsumer' => 'baz', # baz queue consumer for BarMailer + 'AdvancedSneakersActiveJob::DefaultConsumer' => 'default', # default consumer + 'AdvancedSneakersActiveJob::MailersConsumer' => 'mailers', # action mailer consumer + 'AdvancedSneakersActiveJob::CustomConsumer' => 'custom', # see CustomQueueJob in spec/apps/app/jobs } expect(subject.first).to eq(expected_consumers)