diff --git a/README.md b/README.md index c683b8c3c..8ce8bfb3a 100644 --- a/README.md +++ b/README.md @@ -511,12 +511,22 @@ class MyJob < ApplicationJob # A unique key to be globally locked against. # Can be String or Lambda/Proc that is invoked in the context of the job. + # + # If a key is not provided GoodJob will use the job class name. + # + # To disable concurrency control, for example in a subclass, set the + # key explicitly to nil (e.g. `key: nil` or `key: -> { nil }`) + # + # If you provide a custom concurrency key (for example, if one of your arguments + # is transient) make sure that it is sufficiently unique across jobs and queues + # by adding the job class or queue to the key yourself, if needed. + # # Note: Arguments passed to #perform_later can be accessed through Active Job's `arguments` method # which is an array containing positional arguments and, optionally, a kwarg hash. - key: -> { "MyJob-#{arguments.first}-#{arguments.last[:version]}" } # MyJob.perform_later("Alice", version: 'v2') => "MyJob-Alice-v2" + key: -> { "#{self.class.name}-#{queue_name}-#{arguments.first}-#{arguments.last[:version]}" } # MyJob.perform_later("Alice", version: 'v2') => "MyJob-default-Alice-v2" ) - def perform(first_name) + def perform(first_name, version:) # do work end end @@ -525,8 +535,8 @@ end When testing, the resulting concurrency key value can be inspected: ```ruby -job = MyJob.perform_later("Alice") -job.good_job_concurrency_key #=> "MyJob-Alice" +job = MyJob.perform_later("Alice", version: 'v1') +job.good_job_concurrency_key #=> "MyJob-default-Alice-v1" ``` #### How concurrency controls work diff --git a/lib/good_job/active_job_extensions/concurrency.rb b/lib/good_job/active_job_extensions/concurrency.rb index 983e4396b..4e4dc1b17 100644 --- a/lib/good_job/active_job_extensions/concurrency.rb +++ b/lib/good_job/active_job_extensions/concurrency.rb @@ -96,6 +96,8 @@ def good_job_concurrency_key # Generates the concurrency key from the configuration # @return [Object] concurrency key def _good_job_concurrency_key + return _good_job_default_concurrency_key unless self.class.good_job_concurrency_config.key?(:key) + key = self.class.good_job_concurrency_config[:key] return if key.blank? @@ -105,6 +107,12 @@ def _good_job_concurrency_key key end + # Generates the default concurrency key when the configuration doesn't provide one + # @return [String] concurrency key + def _good_job_default_concurrency_key + self.class.name.to_s + end + private def good_job_enqueue_concurrency_check(job, on_abort:, on_enqueue:) diff --git a/spec/lib/good_job/active_job_extensions/concurrency_spec.rb b/spec/lib/good_job/active_job_extensions/concurrency_spec.rb index d962a4000..0a73010e5 100644 --- a/spec/lib/good_job/active_job_extensions/concurrency_spec.rb +++ b/spec/lib/good_job/active_job_extensions/concurrency_spec.rb @@ -26,7 +26,7 @@ def perform(name:) end end - describe 'when concurrency key is nil' do + describe 'when concurrency key returns nil' do it 'does not limit concurrency' do TestJob.good_job_control_concurrency_with( total_limit: -> { 1 }, @@ -38,6 +38,18 @@ def perform(name:) end end + describe 'when concurrency key is nil' do + it 'does not limit concurrency' do + TestJob.good_job_control_concurrency_with( + total_limit: -> { 1 }, + key: nil + ) + + expect(TestJob.perform_later(name: "Alice")).to be_present + expect(TestJob.perform_later(name: "Alice")).to be_present + end + end + describe '.good_job_control_concurrency_with' do describe 'total_limit:', :skip_rails_5 do before do @@ -194,6 +206,22 @@ def perform end end + context 'when no key is specified' do + before do + stub_const 'TestJob', (Class.new(ActiveJob::Base) do + include GoodJob::ActiveJobExtensions::Concurrency + + def perform(name) + end + end) + end + + it 'uses the class name as the default concurrency key' do + job = TestJob.perform_later("Alice") + expect(job.good_job_concurrency_key).to eq('TestJob') + end + end + describe '#perform_later' do before do stub_const 'TestJob', (Class.new(ActiveJob::Base) do