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 4 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
29 changes: 21 additions & 8 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)
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,30 @@ def display_name
end
end

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

# In ruby 3 we need to explicitly separate regular args from the keyword-args.
def perform
object.send(method_name, *args) if object
object.send(method_name, *args, **kwargs) if object
end

def method(sym)
object.method(sym)
end

# rubocop:disable MethodMissing
def method_missing(symbol, *args)
object.send(symbol, *args)
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
114 changes: 113 additions & 1 deletion spec/performable_method_spec.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
require 'helper'
require 'action_controller/metal/strong_parameters' if ActionPack::VERSION::MAJOR >= 5

describe Delayed::PerformableMethod do
describe 'perform' do
before do
@method = Delayed::PerformableMethod.new('foo', :count, ['o'])
korny marked this conversation as resolved.
Show resolved Hide resolved
@method = Delayed::PerformableMethod.new('foo', :count, 'o')
end

context 'with the persisted record cannot be found' do
Expand All @@ -22,6 +23,117 @@
end
end

describe 'perform with positional argument' 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 argument 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 postional hash argument' 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

if ActionPack::VERSION::MAJOR >= 5
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
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 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 Down
Loading