diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d6069cd..9b1deee 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,9 @@ name: Main -on: [push,pull_request] +on: + push: + branches: [main] + pull_request: jobs: build: @@ -20,23 +23,52 @@ jobs: strategy: matrix: ruby: - - '2.5' - - '2.6' - '2.7' - '3.0' + - '3.1' + - '3.2' + - '3.3' + - '3.4' 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 + - gemfiles/activejob_7.2.x.gemfile exclude: - - ruby: '2.5' - gemfile: gemfiles/activejob_7.0.x.gemfile - - ruby: '2.6' + # --- Rails 7.2 (Requires Ruby 3.1+) --- + # Exclude Ruby 2.7 to 3.0 + - ruby: '2.7' + gemfile: gemfiles/activejob_7.2.x.gemfile + - ruby: '3.0' + gemfile: gemfiles/activejob_7.2.x.gemfile + + # --- Rails 7.0 (Upper Bounds) --- + # Rails 7.0 does not officially support Ruby 3.4+ + - ruby: '3.4' gemfile: gemfiles/activejob_7.0.x.gemfile + # --- Rails 6.0 & 6.1 (Upper Bounds) --- + # Rails 6.0 and 6.1 do not officially support Ruby 3.1+ (many gems break) + - ruby: '3.1' + gemfile: gemfiles/activejob_6.0.x.gemfile + - ruby: '3.1' + gemfile: gemfiles/activejob_6.1.x.gemfile + - ruby: '3.2' + gemfile: gemfiles/activejob_6.0.x.gemfile + - ruby: '3.2' + gemfile: gemfiles/activejob_6.1.x.gemfile + - ruby: '3.3' + gemfile: gemfiles/activejob_6.0.x.gemfile + - ruby: '3.3' + gemfile: gemfiles/activejob_6.1.x.gemfile + - ruby: '3.4' + gemfile: gemfiles/activejob_6.0.x.gemfile + - ruby: '3.4' + gemfile: gemfiles/activejob_6.1.x.gemfile steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v5 - name: Install dependencies env: diff --git a/Appraisals b/Appraisals index 3a5d841..4ba64cf 100644 --- a/Appraisals +++ b/Appraisals @@ -14,3 +14,13 @@ 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' + gem 'concurrent-ruby', '1.3.4' +end + +appraise 'activejob-7.2.x' do + gem 'rails', '~> 7.2.0' + gem 'concurrent-ruby', '1.3.4' +end diff --git a/CHANGELOG.md b/CHANGELOG.md index e523cb0..26f5401 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased](https://github.com/veeqo/advanced-sneakers-activejob/compare/v0.6.0...HEAD) +### Added +- [#42](https://github.com/veeqo/advanced-sneakers-activejob/pull/42) Add support for ActiveJob v7.1 and v7.2 (including ActionMailer queue consumer detection) +- [#42](https://github.com/veeqo/advanced-sneakers-activejob/pull/42) Add Ruby 3.3 and 3.4 to CI matrix + +### Changed +- [#42](https://github.com/veeqo/advanced-sneakers-activejob/pull/42) Bump minimum Ruby version from 2.5 to 2.7 + +### Removed +- [#42](https://github.com/veeqo/advanced-sneakers-activejob/pull/42) Drop support for Ruby 2.5 and 2.6 + ## [0.6.0](https://github.com/veeqo/advanced-sneakers-activejob/compare/v0.5.0...v0.6.0) - 2022-02-15 diff --git a/advanced-sneakers-activejob.gemspec b/advanced-sneakers-activejob.gemspec index e34964c..1c7b4d6 100644 --- a/advanced-sneakers-activejob.gemspec +++ b/advanced-sneakers-activejob.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |spec| spec.files = Dir['CHANGELOG.md', 'LICENSE.txt', 'README.md', 'lib/**/*'] - spec.required_ruby_version = '>= 2.5' + spec.required_ruby_version = '>= 2.7' spec.require_paths = ['lib'] diff --git a/gemfiles/activejob_4.2.x.gemfile b/gemfiles/activejob_7.1.x.gemfile similarity index 62% rename from gemfiles/activejob_4.2.x.gemfile rename to gemfiles/activejob_7.1.x.gemfile index 8003ec8..7f20c09 100644 --- a/gemfiles/activejob_4.2.x.gemfile +++ b/gemfiles/activejob_7.1.x.gemfile @@ -2,6 +2,7 @@ source "https://rubygems.org" -gem "rails", "~> 4.2.11" +gem "rails", "~> 7.1.0" +gem "concurrent-ruby", "1.3.4" gemspec path: "../" diff --git a/gemfiles/activejob_5.2.x.gemfile b/gemfiles/activejob_7.2.x.gemfile similarity index 62% rename from gemfiles/activejob_5.2.x.gemfile rename to gemfiles/activejob_7.2.x.gemfile index f647cac..54b6f85 100644 --- a/gemfiles/activejob_5.2.x.gemfile +++ b/gemfiles/activejob_7.2.x.gemfile @@ -2,6 +2,7 @@ source "https://rubygems.org" -gem "rails", "~> 5.2.4" +gem "rails", "~> 7.2.0" +gem "concurrent-ruby", "1.3.4" gemspec path: "../" diff --git a/lib/advanced_sneakers_activejob/workers_registry.rb b/lib/advanced_sneakers_activejob/workers_registry.rb index 44060cc..03d5c5e 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.presence, mailer.deliver_later_queue_name.to_s].compact.join(ActiveJob::Base.queue_name_delimiter) + end end end diff --git a/spec/advanced_sneakers_activejob/workers_registry/method_missing_spec.rb b/spec/advanced_sneakers_activejob/workers_registry/method_missing_spec.rb index 17c22a6..e9a343a 100644 --- a/spec/advanced_sneakers_activejob/workers_registry/method_missing_spec.rb +++ b/spec/advanced_sneakers_activejob/workers_registry/method_missing_spec.rb @@ -21,7 +21,7 @@ subject { registry.foobar } it 'raises NoMethodError' do - expect { subject }.to raise_error(NoMethodError, /undefined method `foobar'/) + expect { subject }.to raise_error(NoMethodError, /undefined method [`']foobar'/) end end end diff --git a/spec/apps/with_advanced_sneakers_adapter.rb b/spec/apps/with_advanced_sneakers_adapter.rb index f67694f..912658b 100644 --- a/spec/apps/with_advanced_sneakers_adapter.rb +++ b/spec/apps/with_advanced_sneakers_adapter.rb @@ -3,6 +3,7 @@ require 'bundler/setup' require 'rails' require 'active_job/railtie' +require 'action_controller/railtie' require 'action_mailer/railtie' unless ENV['SKIP_MAILER'] $LOAD_PATH.unshift File.expand_path('../../../lib', __dir__) diff --git a/spec/apps/with_inline_adapter.rb b/spec/apps/with_inline_adapter.rb index 8683b4c..7291bd8 100644 --- a/spec/apps/with_inline_adapter.rb +++ b/spec/apps/with_inline_adapter.rb @@ -3,6 +3,7 @@ require 'bundler/setup' require 'rails' require 'active_job/railtie' +require 'action_controller/railtie' require 'action_mailer/railtie' unless ENV['SKIP_MAILER'] $LOAD_PATH.unshift File.expand_path('../../../lib', __dir__) diff --git a/spec/apps/with_sneakers_adapter.rb b/spec/apps/with_sneakers_adapter.rb index ff63271..5c20d4d 100644 --- a/spec/apps/with_sneakers_adapter.rb +++ b/spec/apps/with_sneakers_adapter.rb @@ -3,6 +3,7 @@ require 'bundler/setup' require 'rails' require 'active_job/railtie' +require 'action_controller/railtie' require 'action_mailer/railtie' unless ENV['SKIP_MAILER'] require 'sneakers' diff --git a/spec/integration/action_mailer_spec.rb b/spec/integration/action_mailer_spec.rb index 209fd93..19a15ed 100644 --- a/spec/integration/action_mailer_spec.rb +++ b/spec/integration/action_mailer_spec.rb @@ -9,7 +9,7 @@ in_app_process(adapter: :sneakers) { SampleMailer.greetings(name: 'Sneakers').deliver_later } expect_logs name: 'rails', - to_include: /Enqueued ActionMailer::(Mail)?DeliveryJob to Sneakers\(mailers\) with arguments: "SampleMailer", "greetings", "deliver_now", .*\{:name=>"Sneakers"\}/, + to_include: /Enqueued ActionMailer::(Mail)?DeliveryJob to Sneakers\(mailers\) with arguments: "SampleMailer", "greetings", "deliver_now", .*\{(?::name=>|name: )"Sneakers"\}/, to_exclude: [ 'Hello, Sneakers', /Performed ActionMailer::(Mail)?DeliveryJob/ @@ -25,7 +25,7 @@ expect_logs name: 'rails', to_include: [ - /Enqueued ActionMailer::(Mail)?DeliveryJob to AdvancedSneakers\(mailers\) with arguments: "SampleMailer", "greetings", "deliver_now", .*\{:name=>"Advanced sneakers"\}/, + /Enqueued ActionMailer::(Mail)?DeliveryJob to AdvancedSneakers\(mailers\) with arguments: "SampleMailer", "greetings", "deliver_now", .*\{(?::name=>|name: )"Advanced sneakers"\}/, 'Hello, Advanced sneakers', /Performed ActionMailer::(Mail)?DeliveryJob/ ] 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)