Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support ruby 3 and kwargs (latest) #1

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ group :test do
end
end

group :development, :test do
gem 'pry'
end

korny marked this conversation as resolved.
Show resolved Hide resolved
group :rubocop do
gem 'rubocop', '>= 0.25', '< 0.49'
end
Expand Down
4 changes: 2 additions & 2 deletions lib/delayed/message_sending.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/delayed/performable_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 37 additions & 9 deletions lib/delayed/performable_method.rb
Original file line number Diff line number Diff line change
@@ -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 = {})
mokus80 marked this conversation as resolved.
Show resolved Hide resolved
raise NoMethodError, "undefined method `#{method_name}' for #{object.inspect}" unless object.respond_to?(method_name, true)

if object.respond_to?(:persisted?) && !object.persisted?
Expand All @@ -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

Expand All @@ -22,18 +23,45 @@ def display_name
end
end

def perform
object.send(method_name, *args) if object
def kwargs
# Default to a hash so that we can handle deserializing jobs that were
# created before kwargs was available.
@kwargs || {}
NourEldinShobier marked this conversation as resolved.
Show resolved Hide resolved
end

def method(sym)
object.method(sym)
# 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

arguments = args.is_a?(Array) ? args : [args]

if kwargs.present?
object.send(method_name, *arguments, kwargs)
else
object.send(method_name, *arguments)
end
end
end

# rubocop:disable MethodMissing
def method_missing(symbol, *args)
object.send(symbol, *args)
def method(sym)
object.method(sym)
end
method_def = []
location = caller_locations(1, 1).first
file = location.path
line = location.lineno
definition = RUBY_VERSION >= '2.7' ? '...' : '*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)
Expand Down
3 changes: 2 additions & 1 deletion lib/delayed/psych_ext.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ def encode_with(coder)
coder.map = {
'object' => object,
'method_name' => method_name,
'args' => args
'args' => args,
'kwargs' => kwargs
}
end
end
Expand Down
2 changes: 2 additions & 0 deletions spec/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
require 'delayed_job'
require 'delayed/backend/shared_spec'

require 'pry'

korny marked this conversation as resolved.
Show resolved Hide resolved
if ENV['DEBUG_LOGS']
Delayed::Worker.logger = Logger.new(STDOUT)
else
Expand Down
16 changes: 15 additions & 1 deletion spec/message_sending_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
124 changes: 124 additions & 0 deletions spec/performable_method_spec.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'helper'
require 'action_controller/metal/strong_parameters'

describe Delayed::PerformableMethod do
describe 'perform' do
Expand All @@ -22,6 +23,82 @@
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 sample object and options hash' 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 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 positional hash argument and options hash' 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 }])
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 hash to named parameters' do
before do
klass = Class.new do
def test_method(name:, any:)
true if name && any
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
Delayed::PerformableMethod.new(Object, :method_that_does_not_exist, [])
Expand All @@ -46,6 +123,53 @@ def private_method; end
end
end

context 'with params object' do
let(:params) {
ActionController::Parameters.new(:person => {
:name => 'Francesco',
:age => 22,
:role => 'admin'
})
}

describe 'perform with params object' do
it 'calls the method on the object' do
@method = Delayed::PerformableMethod.new('foo', :count, params)
expect(@method.object).to receive(:count).with(params)
@method.perform
end
end

describe 'perform with params object and keyword argument' do
it 'calls the method on the object' do
@method = Delayed::PerformableMethod.new('foo', :count, params, country: 'Spain')
expect(@method.object).to receive(:count).with(params, country: 'Spain')
@method.perform
end
end

describe 'perform with sample object and params object' do
before do
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
end

describe 'hooks' do
%w[before after success].each do |hook|
it "delegates #{hook} hook to object" do
Expand Down
Loading