diff --git a/Changes.md b/Changes.md index d767bfc06..7c103e38c 100644 --- a/Changes.md +++ b/Changes.md @@ -26,6 +26,7 @@ bin/rails generate sidekiq:job ProcessOrderJob ``` - Fix job retries losing CurrentAttributes [#5090] - Tweak shutdown to give long-running threads time to cleanup [#5095] +- Add keyword arguments support in extensions 6.3.1 --------- diff --git a/lib/sidekiq/extensions/action_mailer.rb b/lib/sidekiq/extensions/action_mailer.rb index 444b82aa1..f76ac58aa 100644 --- a/lib/sidekiq/extensions/action_mailer.rb +++ b/lib/sidekiq/extensions/action_mailer.rb @@ -16,8 +16,8 @@ class DelayedMailer include Sidekiq::Worker def perform(yml) - (target, method_name, args) = YAML.load(yml) - msg = target.public_send(method_name, *args) + (target, method_name, args, kwargs) = YAML.load(yml) + msg = kwargs.empty? ? target.public_send(method_name, *args) : target.public_send(method_name, *args, **kwargs) # The email method can return nil, which causes ActionMailer to return # an undeliverable empty message. if msg diff --git a/lib/sidekiq/extensions/active_record.rb b/lib/sidekiq/extensions/active_record.rb index 5342ed851..6ce9e78ba 100644 --- a/lib/sidekiq/extensions/active_record.rb +++ b/lib/sidekiq/extensions/active_record.rb @@ -18,8 +18,8 @@ class DelayedModel include Sidekiq::Worker def perform(yml) - (target, method_name, args) = YAML.load(yml) - target.__send__(method_name, *args) + (target, method_name, args, kwargs) = YAML.load(yml) + kwargs.empty? ? target.__send__(method_name, *args) : target.__send__(method_name, *args, **kwargs) end end diff --git a/lib/sidekiq/extensions/class_methods.rb b/lib/sidekiq/extensions/class_methods.rb index 1723b6f85..0db9d8791 100644 --- a/lib/sidekiq/extensions/class_methods.rb +++ b/lib/sidekiq/extensions/class_methods.rb @@ -16,8 +16,8 @@ class DelayedClass include Sidekiq::Worker def perform(yml) - (target, method_name, args) = YAML.load(yml) - target.__send__(method_name, *args) + (target, method_name, args, kwargs) = YAML.load(yml) + kwargs.empty? ? target.__send__(method_name, *args) : target.__send__(method_name, *args, **kwargs) end end diff --git a/lib/sidekiq/extensions/generic_proxy.rb b/lib/sidekiq/extensions/generic_proxy.rb index 313dd48ca..8408f4daf 100644 --- a/lib/sidekiq/extensions/generic_proxy.rb +++ b/lib/sidekiq/extensions/generic_proxy.rb @@ -13,13 +13,13 @@ def initialize(performable, target, options = {}) @opts = options end - def method_missing(name, *args) + def method_missing(name, *args, **kwargs) # Sidekiq has a limitation in that its message must be JSON. # JSON can't round trip real Ruby objects so we use YAML to # serialize the objects to a String. The YAML will be converted # to JSON and then deserialized on the other side back into a # Ruby object. - obj = [@target, name, args] + obj = [@target, name, args, kwargs] marshalled = ::YAML.dump(obj) if marshalled.size > SIZE_LIMIT ::Sidekiq.logger.warn { "#{@target}.#{name} job argument is #{marshalled.bytesize} bytes, you should refactor it to reduce the size" } diff --git a/test/test_extensions.rb b/test/test_extensions.rb index dd9f65a38..7d0dcde62 100644 --- a/test/test_extensions.rb +++ b/test/test_extensions.rb @@ -14,6 +14,10 @@ class MyModel < ActiveRecord::Base def self.long_class_method raise "Should not be called!" end + + def self.long_class_method_with_optional_args(*arg, **kwargs) + kwargs + end end it 'allows delayed execution of ActiveRecord class methods' do @@ -25,6 +29,23 @@ def self.long_class_method assert_equal 1, q.size end + it 'allows delayed execution of ActiveRecord class methods with optional arguments' do + assert_equal [], Sidekiq::Queue.all.map(&:name) + q = Sidekiq::Queue.new + assert_equal 0, q.size + MyModel.delay.long_class_method_with_optional_args(with: :keywords) + assert_equal ['default'], Sidekiq::Queue.all.map(&:name) + assert_equal 1, q.size + obj = YAML.load q.first['args'].first + assert_equal({ with: :keywords }, obj.last) + end + + it 'forwards the keyword arguments to perform' do + yml = "---\n- !ruby/class 'MyModel'\n- :long_class_method_with_optional_args\n- []\n- :with: :keywords\n" + result = Sidekiq::Extensions::DelayedClass.new.perform(yml) + assert_equal({ with: :keywords }, result) + end + it 'uses and stringifies specified options' do assert_equal [], Sidekiq::Queue.all.map(&:name) q = Sidekiq::Queue.new('notdefault') @@ -53,6 +74,9 @@ class UserMailer < ActionMailer::Base def greetings(a, b) raise "Should not be called!" end + + def greetings_with_optional_args(*arg, **kwargs) + end end it 'allows delayed delivery of ActionMailer mails' do @@ -64,6 +88,17 @@ def greetings(a, b) assert_equal 1, q.size end + it 'allows delayed delivery of ActionMailer mails with optional arguments' do + assert_equal [], Sidekiq::Queue.all.map(&:name) + q = Sidekiq::Queue.new + assert_equal 0, q.size + UserMailer.delay.greetings_with_optional_args(with: :keywords) + assert_equal ['default'], Sidekiq::Queue.all.map(&:name) + assert_equal 1, q.size + obj = YAML.load q.first['args'].first + assert_equal({ with: :keywords }, obj.last) + end + it 'allows delayed scheduling of AM mails' do ss = Sidekiq::ScheduledSet.new assert_equal 0, ss.size @@ -81,6 +116,10 @@ def greetings(a, b) class SomeClass def self.doit(arg) end + + def self.doit_with_optional_args(*arg, **kwargs) + kwargs + end end it 'allows delay of any ole class method' do @@ -90,9 +129,28 @@ def self.doit(arg) assert_equal 1, q.size end + it 'allows delay of any ole class method with optional arguments' do + q = Sidekiq::Queue.new + assert_equal 0, q.size + SomeClass.delay.doit_with_optional_args(with: :keywords) + assert_equal 1, q.size + obj = YAML.load q.first['args'].first + assert_equal({ with: :keywords }, obj.last) + end + + it 'forwards the keyword arguments to perform' do + yml = "---\n- !ruby/class 'SomeClass'\n- :doit_with_optional_args\n- []\n- :with: :keywords\n" + result = Sidekiq::Extensions::DelayedClass.new.perform(yml) + assert_equal({ with: :keywords }, result) + end + module SomeModule def self.doit(arg) end + + def self.doit_with_optional_args(*arg, **kwargs) + kwargs + end end it 'logs large payloads' do @@ -109,4 +167,18 @@ def self.doit(arg) assert_equal 1, q.size end + it 'allows delay of any module class method with optional arguments' do + q = Sidekiq::Queue.new + assert_equal 0, q.size + SomeModule.delay.doit_with_optional_args(with: :keywords) + assert_equal 1, q.size + obj = YAML.load q.first['args'].first + assert_equal({ with: :keywords }, obj.last) + end + + it 'forwards the keyword arguments to perform' do + yml = "---\n- !ruby/class 'SomeModule'\n- :doit_with_optional_args\n- []\n- :with: :keywords\n" + result = Sidekiq::Extensions::DelayedClass.new.perform(yml) + assert_equal({ with: :keywords }, result) + end end