From d5e43b4e42fb85fd338bcede068d5332df76f31a Mon Sep 17 00:00:00 2001 From: Christian Eichhorn Date: Tue, 22 Dec 2020 11:28:16 +0100 Subject: [PATCH 01/15] * Support for Ruby 3.0 added --- lib/delayed/performable_mailer.rb | 2 +- lib/delayed/performable_method.rb | 32 +++++++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/lib/delayed/performable_mailer.rb b/lib/delayed/performable_mailer.rb index 8535c452d..7c5d6d7b0 100644 --- a/lib/delayed/performable_mailer.rb +++ b/lib/delayed/performable_mailer.rb @@ -3,7 +3,7 @@ module Delayed class PerformableMailer < PerformableMethod def perform - mailer = object.send(method_name, *args) + mailer = super mailer.respond_to?(:deliver_now) ? mailer.deliver_now : mailer.deliver end end diff --git a/lib/delayed/performable_method.rb b/lib/delayed/performable_method.rb index 96a28b056..7941d3826 100644 --- a/lib/delayed/performable_method.rb +++ b/lib/delayed/performable_method.rb @@ -22,8 +22,26 @@ def display_name end end - def perform - object.send(method_name, *args) if object + if RUBY_VERSION >= '3.0' + def perform + if args_is_a_hash? + object.send(method_name, **args.first) + else + object.send(method_name, *args) + end if object + rescue => e + p e.message + p args + raise e + end + + def args_is_a_hash? + args.size == 1 && args.first.is_a?(Hash) + end + else + def perform + object.send(method_name, *args) if object + end end def method(sym) @@ -31,8 +49,14 @@ def method(sym) end # rubocop:disable MethodMissing - def method_missing(symbol, *args) - object.send(symbol, *args) + if RUBY_VERSION >= '3.0' + def method_missing(symbol, ...) + object.send(symbol, ...) + end + else + def method_missing(symbol, *args) + object.send(symbol, *args) + end end # rubocop:enable MethodMissing From 7c73130d74f4628d9c6bc500e0fae0e14842bf84 Mon Sep 17 00:00:00 2001 From: Christian Eichhorn Date: Tue, 22 Dec 2020 15:01:19 +0100 Subject: [PATCH 02/15] * debug messages commented out --- lib/delayed/performable_method.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/delayed/performable_method.rb b/lib/delayed/performable_method.rb index 7941d3826..b5315717b 100644 --- a/lib/delayed/performable_method.rb +++ b/lib/delayed/performable_method.rb @@ -29,10 +29,10 @@ def perform else object.send(method_name, *args) end if object - rescue => e - p e.message - p args - raise e +# rescue => e +# p e.message +# p args +# raise e end def args_is_a_hash? From bfdabfe68134202f302507b3db1567d910386623 Mon Sep 17 00:00:00 2001 From: Christian Eichhorn Date: Tue, 22 Dec 2020 15:25:35 +0100 Subject: [PATCH 03/15] * syntax fixed --- lib/delayed/performable_method.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/delayed/performable_method.rb b/lib/delayed/performable_method.rb index b5315717b..1cb192ac2 100644 --- a/lib/delayed/performable_method.rb +++ b/lib/delayed/performable_method.rb @@ -50,8 +50,8 @@ def method(sym) # rubocop:disable MethodMissing if RUBY_VERSION >= '3.0' - def method_missing(symbol, ...) - object.send(symbol, ...) + def method_missing(...) + object.send(...) end else def method_missing(symbol, *args) From 90faa82d9fc1f65a610f981eb4401f220ad9e9c6 Mon Sep 17 00:00:00 2001 From: Christian Eichhorn Date: Tue, 22 Dec 2020 15:51:56 +0100 Subject: [PATCH 04/15] * Syntax fixed --- lib/delayed/performable_method.rb | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/delayed/performable_method.rb b/lib/delayed/performable_method.rb index 1cb192ac2..c13c8a00c 100644 --- a/lib/delayed/performable_method.rb +++ b/lib/delayed/performable_method.rb @@ -49,15 +49,11 @@ def method(sym) end # rubocop:disable MethodMissing - if RUBY_VERSION >= '3.0' - def method_missing(...) - object.send(...) - end - else - def method_missing(symbol, *args) - object.send(symbol, *args) - end - end + definition = RUBY_VERSION >= '3.0' ? '...' : '*args, &block' + method_def << + "def method_missing(#{definition})" << + " object.send(#{definition})" << + "end" # rubocop:enable MethodMissing def respond_to?(symbol, include_private = false) From 34dc5c9e6339724d92aaef4688e16b3b8b3eecc3 Mon Sep 17 00:00:00 2001 From: Christian Eichhorn Date: Tue, 22 Dec 2020 15:56:47 +0100 Subject: [PATCH 05/15] * Syntax fixed --- lib/delayed/performable_method.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/delayed/performable_method.rb b/lib/delayed/performable_method.rb index c13c8a00c..ee2084684 100644 --- a/lib/delayed/performable_method.rb +++ b/lib/delayed/performable_method.rb @@ -49,11 +49,15 @@ def method(sym) end # rubocop:disable MethodMissing + method_def = [] + location = caller_locations(1, 1).first + file, line = location.path, location.lineno definition = RUBY_VERSION >= '3.0' ? '...' : '*args, &block' method_def << "def method_missing(#{definition})" << " object.send(#{definition})" << "end" + module_eval(method_def.join(";"), file, line) # rubocop:enable MethodMissing def respond_to?(symbol, include_private = false) From c93b433fab05e2e25a5b36da3ecd41dd7845920d Mon Sep 17 00:00:00 2001 From: Christian Eichhorn Date: Sat, 27 Mar 2021 02:17:44 +0100 Subject: [PATCH 06/15] * specs added --- lib/delayed/performable_method.rb | 4 ++ spec/performable_method_spec.rb | 103 ++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/lib/delayed/performable_method.rb b/lib/delayed/performable_method.rb index ee2084684..2f4d41eb1 100644 --- a/lib/delayed/performable_method.rb +++ b/lib/delayed/performable_method.rb @@ -22,6 +22,10 @@ def display_name end end + # required to support named parameters in RUBY 3.0 + # Otherwise the following error is thrown + # ArgumentError: + # wrong number of arguments (given 1, expected 0; required keywords: if RUBY_VERSION >= '3.0' def perform if args_is_a_hash? diff --git a/spec/performable_method_spec.rb b/spec/performable_method_spec.rb index a4d700322..87e50d860 100644 --- a/spec/performable_method_spec.rb +++ b/spec/performable_method_spec.rb @@ -1,4 +1,5 @@ require 'helper' +require 'action_controller/metal/strong_parameters' describe Delayed::PerformableMethod do describe 'perform' do @@ -21,6 +22,108 @@ @method.perform end end + + describe 'perform with hash object' do + before do + @method = Delayed::PerformableMethod.new('foo', :count, [{o: true}]) + end + + it 'calls the method on the object' do + expect(@method.object).to receive(:count).with(o: true) + @method.perform + end + end + + describe 'perform with many hash objects' do + before do + @method = Delayed::PerformableMethod.new('foo', :count, [{o: true}, {o2: true}]) + end + + it 'calls the method on the object' do + expect(@method.object).to receive(:count).with({o: true}, {o2: true}) + @method.perform + end + end + + describe 'perform with params object' do + before do + @params = ActionController::Parameters.new({ + person: { + name: "Francesco", + age: 22, + role: "admin" + } + }) + + @method = Delayed::PerformableMethod.new('foo', :count, [@params]) + end + + it 'calls the method on the object' do + expect(@method.object).to receive(:count).with(@params) + @method.perform + end + end + + describe 'perform with sample object and params object' do + before do + @params = ActionController::Parameters.new({ + person: { + name: "Francesco", + age: 22, + role: "admin" + } + }) + + klass = Class.new do + def test_method(o1, o2) + true + end + end + + @method = Delayed::PerformableMethod.new(klass.new, :test_method, ['o', @params]) + end + + it 'calls the method on the object' do + expect(@method.object).to receive(:test_method).with('o', @params) + @method.perform + end + + it 'calls the method on the object (real)' do + expect(@method.perform).to be true + end + end + + describe 'perform with sample object and hash object' do + before do + @method = Delayed::PerformableMethod.new('foo', :count, ['o', {o: true}]) + end + + it 'calls the method on the object' do + expect(@method.object).to receive(:count).with('o', {o: true}) + @method.perform + end + end + + describe 'perform with hash to named parameters' do + before do + klass = Class.new do + def test_method(name:, any:) + true + end + end + + @method = Delayed::PerformableMethod.new(klass.new, :test_method, [{name: 'name', any: 'any'}]) + end + + it 'calls the method on the object' do + expect(@method.object).to receive(:test_method).with({name: 'name', any: 'any'}) + @method.perform + end + + it 'calls the method on the object (real)' do + expect(@method.perform).to be true + end + end it "raises a NoMethodError if target method doesn't exist" do expect do From 87a2aefd77017e6bbdc2e9052337e37b0c2c65d3 Mon Sep 17 00:00:00 2001 From: Christian Eichhorn Date: Sat, 27 Mar 2021 02:21:08 +0100 Subject: [PATCH 07/15] * Ruby 3.0 added --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f78e0b26..6fecf10ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - ruby: [2.5, 2.6, 2.7, jruby, jruby-head, ruby-head] + ruby: [2.5, 2.6, 2.7, 3.0, jruby, jruby-head, ruby-head] rails_version: - '5.2.0' - '6.0.0' From 8903aaa619db567c4575c06071e1ca0610a45260 Mon Sep 17 00:00:00 2001 From: Christian Eichhorn Date: Sun, 28 Mar 2021 00:40:55 +0100 Subject: [PATCH 08/15] * ci fixed --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6fecf10ea..7fd1e8544 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,9 +14,9 @@ jobs: matrix: ruby: [2.5, 2.6, 2.7, 3.0, jruby, jruby-head, ruby-head] rails_version: - - '5.2.0' + - '5.2.5' - '6.0.0' - - '6.1.0.rc2' + - '6.1.0' - 'edge' include: # From 25b0a4316c558ac74026dff8d22dae395e671b77 Mon Sep 17 00:00:00 2001 From: Christian Eichhorn Date: Sun, 28 Mar 2021 00:48:00 +0100 Subject: [PATCH 09/15] * dependencies fixed --- Gemfile | 6 +++--- delayed_job.gemspec | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index b83acb5bc..0bd41d117 100644 --- a/Gemfile +++ b/Gemfile @@ -27,7 +27,7 @@ platforms :jruby do elsif ENV['RAILS_VERSION'] gem 'railties', "~> #{ENV['RAILS_VERSION']}" else - gem 'railties', ['>= 3.0', '< 6.2'] + gem 'railties', ['>= 3.0', '< 7.1'] end end @@ -43,8 +43,8 @@ group :test do gem 'actionmailer', "~> #{ENV['RAILS_VERSION']}" gem 'activerecord', "~> #{ENV['RAILS_VERSION']}" else - gem 'actionmailer', ['>= 3.0', '< 6.2'] - gem 'activerecord', ['>= 3.0', '< 6.2'] + gem 'actionmailer', ['>= 3.0', '< 7.1'] + gem 'activerecord', ['>= 3.0', '< 7.1'] end gem 'rspec', '>= 3' diff --git a/delayed_job.gemspec b/delayed_job.gemspec index c9b60a781..23eec33e6 100644 --- a/delayed_job.gemspec +++ b/delayed_job.gemspec @@ -1,7 +1,7 @@ # -*- encoding: utf-8 -*- Gem::Specification.new do |spec| - spec.add_dependency 'activesupport', ['>= 3.0', '< 6.2'] + spec.add_dependency 'activesupport', ['>= 3.0', '< 7.1'] spec.authors = ['Brandon Keepers', 'Brian Ryckbost', 'Chris Gaffney', 'David Genord II', 'Erik Michaels-Ober', 'Matt Griffin', 'Steve Richert', 'Tobias Lütke'] spec.description = 'Delayed_job (or DJ) encapsulates the common pattern of asynchronously executing longer tasks in the background. It is a direct extraction from Shopify where the job table is responsible for a multitude of core tasks.' spec.email = ['brian@collectiveidea.com'] From 7819cb8cf1726631698eb527055430c723b431af Mon Sep 17 00:00:00 2001 From: Christian Eichhorn Date: Sun, 28 Mar 2021 01:06:31 +0100 Subject: [PATCH 10/15] * rubocop fixed --- lib/delayed/performable_method.rb | 21 +++++------ spec/performable_method_spec.rb | 58 ++++++++++++++----------------- 2 files changed, 36 insertions(+), 43 deletions(-) diff --git a/lib/delayed/performable_method.rb b/lib/delayed/performable_method.rb index 2f4d41eb1..9f64a6167 100644 --- a/lib/delayed/performable_method.rb +++ b/lib/delayed/performable_method.rb @@ -28,15 +28,13 @@ def display_name # wrong number of arguments (given 1, expected 0; required keywords: if RUBY_VERSION >= '3.0' def perform + return unless object + if args_is_a_hash? object.send(method_name, **args.first) else object.send(method_name, *args) - end if object -# rescue => e -# p e.message -# p args -# raise e + end end def args_is_a_hash? @@ -51,17 +49,16 @@ def perform def method(sym) object.method(sym) end - - # rubocop:disable MethodMissing method_def = [] location = caller_locations(1, 1).first - file, line = location.path, location.lineno + file = location.path + line = location.lineno definition = RUBY_VERSION >= '3.0' ? '...' : '*args, &block' method_def << - "def method_missing(#{definition})" << - " object.send(#{definition})" << - "end" - module_eval(method_def.join(";"), file, line) + "def method_missing(#{definition})" \ + " object.send(#{definition})" \ + 'end' + module_eval(method_def.join(';'), file, line) # rubocop:enable MethodMissing def respond_to?(symbol, include_private = false) diff --git a/spec/performable_method_spec.rb b/spec/performable_method_spec.rb index 87e50d860..33d687c96 100644 --- a/spec/performable_method_spec.rb +++ b/spec/performable_method_spec.rb @@ -22,38 +22,36 @@ @method.perform end end - + describe 'perform with hash object' do before do - @method = Delayed::PerformableMethod.new('foo', :count, [{o: true}]) + @method = Delayed::PerformableMethod.new('foo', :count, [{:o => true}]) end it 'calls the method on the object' do - expect(@method.object).to receive(:count).with(o: true) + expect(@method.object).to receive(:count).with(:o => true) @method.perform end end - + describe 'perform with many hash objects' do before do - @method = Delayed::PerformableMethod.new('foo', :count, [{o: true}, {o2: true}]) + @method = Delayed::PerformableMethod.new('foo', :count, [{:o => true}, {:o2 => true}]) end it 'calls the method on the object' do - expect(@method.object).to receive(:count).with({o: true}, {o2: true}) + expect(@method.object).to receive(:count).with({:o => true}, :o2 => true) @method.perform end end - + describe 'perform with params object' do before do - @params = ActionController::Parameters.new({ - person: { - name: "Francesco", - age: 22, - role: "admin" - } - }) + @params = ActionController::Parameters.new(:person => { + :name => 'Francesco', + :age => 22, + :role => 'admin' + }) @method = Delayed::PerformableMethod.new('foo', :count, [@params]) end @@ -63,19 +61,17 @@ @method.perform end end - + describe 'perform with sample object and params object' do before do - @params = ActionController::Parameters.new({ - person: { - name: "Francesco", - age: 22, - role: "admin" - } - }) - + @params = ActionController::Parameters.new(:person => { + :name => 'Francesco', + :age => 22, + :role => 'admin' + }) + klass = Class.new do - def test_method(o1, o2) + def test_method(_o1, _o2) true end end @@ -92,31 +88,31 @@ def test_method(o1, o2) expect(@method.perform).to be true end end - + describe 'perform with sample object and hash object' do before do - @method = Delayed::PerformableMethod.new('foo', :count, ['o', {o: true}]) + @method = Delayed::PerformableMethod.new('foo', :count, ['o', {:o => true}]) end it 'calls the method on the object' do - expect(@method.object).to receive(:count).with('o', {o: true}) + expect(@method.object).to receive(:count).with('o', :o => true) @method.perform end end - + describe 'perform with hash to named parameters' do before do klass = Class.new do def test_method(name:, any:) - true + true if name && any end end - @method = Delayed::PerformableMethod.new(klass.new, :test_method, [{name: 'name', any: 'any'}]) + @method = Delayed::PerformableMethod.new(klass.new, :test_method, [{:name => 'name', :any => 'any'}]) end it 'calls the method on the object' do - expect(@method.object).to receive(:test_method).with({name: 'name', any: 'any'}) + expect(@method.object).to receive(:test_method).with(:name => 'name', :any => 'any') @method.perform end From fff4e1e6d52af4320b152ee5850f3c1207948d46 Mon Sep 17 00:00:00 2001 From: Christian Eichhorn Date: Sun, 28 Mar 2021 01:30:19 +0100 Subject: [PATCH 11/15] * specs for older rails versions fixed --- spec/performable_method_spec.rb | 70 +++++++++++++++++---------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/spec/performable_method_spec.rb b/spec/performable_method_spec.rb index 33d687c96..0d01eb528 100644 --- a/spec/performable_method_spec.rb +++ b/spec/performable_method_spec.rb @@ -1,5 +1,5 @@ require 'helper' -require 'action_controller/metal/strong_parameters' +require 'action_controller/metal/strong_parameters' if ActionPack.version.to_s >= '4.0' describe Delayed::PerformableMethod do describe 'perform' do @@ -45,47 +45,49 @@ end end - describe 'perform with params object' do - before do - @params = ActionController::Parameters.new(:person => { - :name => 'Francesco', - :age => 22, - :role => 'admin' - }) + if ActionPack.version.to_s >= '4.0' + describe 'perform with params object' do + before do + @params = ActionController::Parameters.new(:person => { + :name => 'Francesco', + :age => 22, + :role => 'admin' + }) - @method = Delayed::PerformableMethod.new('foo', :count, [@params]) - end + @method = Delayed::PerformableMethod.new('foo', :count, [@params]) + end - it 'calls the method on the object' do - expect(@method.object).to receive(:count).with(@params) - @method.perform + it 'calls the method on the object' do + expect(@method.object).to receive(:count).with(@params) + @method.perform + end end - end - describe 'perform with sample object and params object' do - before do - @params = ActionController::Parameters.new(:person => { - :name => 'Francesco', - :age => 22, - :role => 'admin' - }) - - klass = Class.new do - def test_method(_o1, _o2) - true + describe 'perform with sample object and params object' do + before do + @params = ActionController::Parameters.new(:person => { + :name => 'Francesco', + :age => 22, + :role => 'admin' + }) + + klass = Class.new do + def test_method(_o1, _o2) + true + end end - end - @method = Delayed::PerformableMethod.new(klass.new, :test_method, ['o', @params]) - end + @method = Delayed::PerformableMethod.new(klass.new, :test_method, ['o', @params]) + end - it 'calls the method on the object' do - expect(@method.object).to receive(:test_method).with('o', @params) - @method.perform - end + it 'calls the method on the object' do + expect(@method.object).to receive(:test_method).with('o', @params) + @method.perform + end - it 'calls the method on the object (real)' do - expect(@method.perform).to be true + it 'calls the method on the object (real)' do + expect(@method.perform).to be true + end end end From 0afc6a2c9005dc5ab868f48196198ac8e1bea761 Mon Sep 17 00:00:00 2001 From: Christian Eichhorn Date: Sun, 28 Mar 2021 01:37:10 +0100 Subject: [PATCH 12/15] * specs for old rails versions fixed --- spec/performable_method_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/performable_method_spec.rb b/spec/performable_method_spec.rb index 0d01eb528..352daf095 100644 --- a/spec/performable_method_spec.rb +++ b/spec/performable_method_spec.rb @@ -1,5 +1,5 @@ require 'helper' -require 'action_controller/metal/strong_parameters' if ActionPack.version.to_s >= '4.0' +require 'action_controller/metal/strong_parameters' if ActionPack::VERSION::MAJOR >= 4 describe Delayed::PerformableMethod do describe 'perform' do @@ -45,7 +45,7 @@ end end - if ActionPack.version.to_s >= '4.0' + if ActionPack::VERSION::MAJOR >= 4 describe 'perform with params object' do before do @params = ActionController::Parameters.new(:person => { From 1e977484cbe57c8f2375b510ef788cc75c536172 Mon Sep 17 00:00:00 2001 From: Christian Eichhorn Date: Sun, 28 Mar 2021 01:44:21 +0100 Subject: [PATCH 13/15] + specs for old rails versions fixed --- spec/performable_method_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/performable_method_spec.rb b/spec/performable_method_spec.rb index 352daf095..17179c676 100644 --- a/spec/performable_method_spec.rb +++ b/spec/performable_method_spec.rb @@ -1,5 +1,5 @@ require 'helper' -require 'action_controller/metal/strong_parameters' if ActionPack::VERSION::MAJOR >= 4 +require 'action_controller/metal/strong_parameters' if ActionPack::VERSION::MAJOR >= 5 describe Delayed::PerformableMethod do describe 'perform' do @@ -45,7 +45,7 @@ end end - if ActionPack::VERSION::MAJOR >= 4 + if ActionPack::VERSION::MAJOR >= 5 describe 'perform with params object' do before do @params = ActionController::Parameters.new(:person => { From e8b520e6d8de953467eba1f3c84dd2015fb55f93 Mon Sep 17 00:00:00 2001 From: Jonathan del Strother Date: Mon, 8 Nov 2021 18:14:00 +0000 Subject: [PATCH 14/15] Capture kwargs separately in PerformableMethod We're going to need to explicitly separate kwargs and hash parameters to fix ruby 3 compatibility --- lib/delayed/message_sending.rb | 4 ++-- lib/delayed/performable_method.rb | 25 ++++++++++++------------- lib/delayed/psych_ext.rb | 3 ++- spec/message_sending_spec.rb | 16 +++++++++++++++- spec/performable_method_spec.rb | 13 ++++++++++++- 5 files changed, 43 insertions(+), 18 deletions(-) diff --git a/lib/delayed/message_sending.rb b/lib/delayed/message_sending.rb index a2808fd39..34cc4f6ee 100644 --- a/lib/delayed/message_sending.rb +++ b/lib/delayed/message_sending.rb @@ -7,8 +7,8 @@ def initialize(payload_class, target, options) end # rubocop:disable MethodMissing - def method_missing(method, *args) - Job.enqueue({:payload_object => @payload_class.new(@target, method.to_sym, args)}.merge(@options)) + def method_missing(method, *args, **kwargs) + Job.enqueue({:payload_object => @payload_class.new(@target, method.to_sym, args, kwargs)}.merge(@options)) end # rubocop:enable MethodMissing end diff --git a/lib/delayed/performable_method.rb b/lib/delayed/performable_method.rb index 9f64a6167..fd5173448 100644 --- a/lib/delayed/performable_method.rb +++ b/lib/delayed/performable_method.rb @@ -1,8 +1,8 @@ module Delayed class PerformableMethod - attr_accessor :object, :method_name, :args + attr_accessor :object, :method_name, :args, :kwargs - def initialize(object, method_name, args) + def initialize(object, method_name, args, kwargs = {}) raise NoMethodError, "undefined method `#{method_name}' for #{object.inspect}" unless object.respond_to?(method_name, true) if object.respond_to?(:persisted?) && !object.persisted? @@ -11,6 +11,7 @@ def initialize(object, method_name, args) self.object = object self.args = args + self.kwargs = kwargs self.method_name = method_name.to_sym end @@ -22,24 +23,22 @@ def display_name end end - # required to support named parameters in RUBY 3.0 - # Otherwise the following error is thrown - # ArgumentError: - # wrong number of arguments (given 1, expected 0; required keywords: + def kwargs + # Default to a hash so that we can handle deserializing jobs that were + # created before kwargs was available. + @kwargs || {} + end + + # In ruby 3 we need to explicitly separate regular args from the keyword-args. if RUBY_VERSION >= '3.0' def perform return unless object - - if args_is_a_hash? - object.send(method_name, **args.first) + if kwargs.present? + object.send(method_name, *args, **kwargs) else object.send(method_name, *args) end end - - def args_is_a_hash? - args.size == 1 && args.first.is_a?(Hash) - end else def perform object.send(method_name, *args) if object diff --git a/lib/delayed/psych_ext.rb b/lib/delayed/psych_ext.rb index 00350a453..049a18d12 100644 --- a/lib/delayed/psych_ext.rb +++ b/lib/delayed/psych_ext.rb @@ -5,7 +5,8 @@ def encode_with(coder) coder.map = { 'object' => object, 'method_name' => method_name, - 'args' => args + 'args' => args, + 'kwargs' => kwargs } end end diff --git a/spec/message_sending_spec.rb b/spec/message_sending_spec.rb index ca58edcc1..281f8b287 100644 --- a/spec/message_sending_spec.rb +++ b/spec/message_sending_spec.rb @@ -64,12 +64,17 @@ def spin; end context 'delay' do class FairyTail - attr_accessor :happy_ending + attr_accessor :happy_ending, :ogre, :dead def self.princesses; end def tell @happy_ending = true end + + def defeat(ogre_params, dead: true) + @ogre = ogre_params + @dead = dead + end end after do @@ -143,5 +148,14 @@ def tell end.to change(fairy_tail, :happy_ending).from(nil).to(true) end.not_to(change { Delayed::Job.count }) end + + it 'can handle a mix of params and kwargs' do + Delayed::Worker.delay_jobs = false + fairy_tail = FairyTail.new + expect do + fairy_tail.delay.defeat({:name => 'shrek'}, :dead => false) + end.to change(fairy_tail, :ogre).from(nil).to(:name => 'shrek'). + and(change(fairy_tail, :dead).from(nil).to(false)) + end end end diff --git a/spec/performable_method_spec.rb b/spec/performable_method_spec.rb index 17179c676..c5d3e7803 100644 --- a/spec/performable_method_spec.rb +++ b/spec/performable_method_spec.rb @@ -34,6 +34,17 @@ end end + describe 'perform with hash object and kwargs' do + before do + @method = Delayed::PerformableMethod.new('foo', :count, [{:o => true}], :o2 => false) + end + + it 'calls the method on the object' do + expect(@method.object).to receive(:count).with({:o => true}, :o2 => false) + @method.perform + end + end + describe 'perform with many hash objects' do before do @method = Delayed::PerformableMethod.new('foo', :count, [{:o => true}, {:o2 => true}]) @@ -110,7 +121,7 @@ def test_method(name:, any:) end end - @method = Delayed::PerformableMethod.new(klass.new, :test_method, [{:name => 'name', :any => 'any'}]) + @method = Delayed::PerformableMethod.new(klass.new, :test_method, [], :name => 'name', :any => 'any') end it 'calls the method on the object' do From f379d6a35f69998f20117b9be0fdcc9dac341398 Mon Sep 17 00:00:00 2001 From: Jonathan del Strother Date: Tue, 9 Nov 2021 09:31:29 +0000 Subject: [PATCH 15/15] Try to maintain support for ruby 2.6 --- lib/delayed/performable_method.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/delayed/performable_method.rb b/lib/delayed/performable_method.rb index fd5173448..0fbb4f038 100644 --- a/lib/delayed/performable_method.rb +++ b/lib/delayed/performable_method.rb @@ -31,18 +31,19 @@ def kwargs # In ruby 3 we need to explicitly separate regular args from the keyword-args. if RUBY_VERSION >= '3.0' + def perform + object.send(method_name, *args, **kwargs) if object + end + else + # On ruby 2, rely on the implicit conversion from a hash to kwargs def perform return unless object if kwargs.present? - object.send(method_name, *args, **kwargs) + object.send(method_name, *args, kwargs) else object.send(method_name, *args) end end - else - def perform - object.send(method_name, *args) if object - end end def method(sym) @@ -52,7 +53,7 @@ def method(sym) location = caller_locations(1, 1).first file = location.path line = location.lineno - definition = RUBY_VERSION >= '3.0' ? '...' : '*args, &block' + definition = RUBY_VERSION >= '2.7' ? '...' : '*args, &block' method_def << "def method_missing(#{definition})" \ " object.send(#{definition})" \