Skip to content

Commit

Permalink
constantize objects when deserializing so that ActiveSupport can atte…
Browse files Browse the repository at this point in the history
…mpt its auto loading magic

Closes collectiveidea#65
  • Loading branch information
bkeepers committed May 19, 2010
1 parent d71cd25 commit 4c53af8
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 88 deletions.
35 changes: 5 additions & 30 deletions lib/delayed/backend/base.rb
Expand Up @@ -50,11 +50,14 @@ def name
end

def payload_object=(object)
self['handler'] = object.to_yaml
self.handler = object.to_yaml
end

def payload_object
@payload_object ||= deserialize(self['handler'])
@payload_object ||= YAML.load(self.handler)
rescue TypeError, LoadError, NameError => e
raise DeserializationError,
"Job failed to load: #{e.message}. Try to manually require the required file. Handler: #{handler.inspect}"
end

# Moved into its own method so that new_relic can trace it.
Expand All @@ -68,34 +71,6 @@ def unlock
self.locked_by = nil
end

private

def deserialize(source)
handler = YAML.load(source) rescue nil

unless handler.respond_to?(:perform)
if handler.nil? && source =~ ParseObjectFromYaml
handler_class = $1
end
attempt_to_load(handler_class || handler.class)
handler = YAML.load(source)
end

return handler if handler.respond_to?(:perform)

raise DeserializationError,
'Job failed to load: Unknown handler. Try to manually require the appropriate file.'
rescue TypeError, LoadError, NameError => e
raise DeserializationError,
"Job failed to load: #{e.message}. Try to manually require the required file."
end

# Constantize the object so that ActiveSupport can attempt
# its auto loading magic. Will raise LoadError if not successful.
def attempt_to_load(klass)
klass.constantize
end

protected

def set_default_run_at
Expand Down
37 changes: 0 additions & 37 deletions lib/delayed/class_to_yaml.rb

This file was deleted.

40 changes: 40 additions & 0 deletions lib/delayed/yaml_ext.rb
@@ -0,0 +1,40 @@
# These extensions allow properly serializing and autoloading of
# Classes, Modules and Structs

require 'yaml'

class Module
yaml_as "tag:ruby.yaml.org,2002:module"

def self.yaml_new(klass, tag, val)
val.constantize
end

def to_yaml( opts = {} )
YAML::quick_emit( nil, opts ) { |out|
out.scalar(taguri, self.name, :plain)
}
end

def yaml_tag_read_class(name)
# Constantize the object so that ActiveSupport can attempt
# its auto loading magic. Will raise LoadError if not successful.
name.constantize
name
end

end

class Class
yaml_as "tag:ruby.yaml.org,2002:class"
remove_method :to_yaml # use Module's to_yaml
end

class Struct
def self.yaml_tag_read_class(name)
# Constantize the object so that ActiveSupport can attempt
# its auto loading magic. Will raise LoadError if not successful.
name.constantize
"Struct::#{ name }"
end
end
2 changes: 1 addition & 1 deletion lib/delayed_job.rb
Expand Up @@ -2,7 +2,7 @@

require File.dirname(__FILE__) + '/delayed/message_sending'
require File.dirname(__FILE__) + '/delayed/performable_method'
require File.dirname(__FILE__) + '/delayed/class_to_yaml'
require File.dirname(__FILE__) + '/delayed/yaml_ext'
require File.dirname(__FILE__) + '/delayed/backend/base'
require File.dirname(__FILE__) + '/delayed/worker'
require File.dirname(__FILE__) + '/delayed/railtie' if defined?(::Rails::Railtie)
Expand Down
7 changes: 7 additions & 0 deletions spec/autoloaded/clazz.rb
@@ -0,0 +1,7 @@
# Make sure this file does not get required manually
module Autoloaded
class Clazz
def perform
end
end
end
7 changes: 7 additions & 0 deletions spec/autoloaded/struct.rb
@@ -0,0 +1,7 @@
# Make sure this file does not get required manually
module Autoloaded
class Struct < ::Struct.new(nil)
def perform
end
end
end
31 changes: 11 additions & 20 deletions spec/backend/shared_backend_spec.rb
Expand Up @@ -53,31 +53,22 @@ def create_job(opts = {})
describe "payload_object" do
it "should raise a DeserializationError when the job class is totally unknown" do
job = @backend.new :handler => "--- !ruby/object:JobThatDoesNotExist {}"
lambda { job.payload_object.perform }.should raise_error(Delayed::Backend::DeserializationError)
end

it "should try to load the class when it is unknown at the time of the deserialization" do
job = @backend.new :handler => "--- !ruby/object:JobThatDoesNotExist {}"
job.should_receive(:attempt_to_load).with('JobThatDoesNotExist').and_return(true)
lambda { job.payload_object.perform }.should raise_error(Delayed::Backend::DeserializationError)
lambda { job.payload_object }.should raise_error(Delayed::Backend::DeserializationError)
end

it "should try include the namespace when loading unknown objects" do
job = @backend.new :handler => "--- !ruby/object:Delayed::JobThatDoesNotExist {}"
job.should_receive(:attempt_to_load).with('Delayed::JobThatDoesNotExist').and_return(true)
lambda { job.payload_object.perform }.should raise_error(Delayed::Backend::DeserializationError)
it "should raise a DeserializationError when the job struct is totally unknown" do
job = @backend.new :handler => "--- !ruby/struct:StructThatDoesNotExist {}"
lambda { job.payload_object }.should raise_error(Delayed::Backend::DeserializationError)
end

it "should also try to load structs when they are unknown (raises TypeError)" do
job = @backend.new :handler => "--- !ruby/struct:JobThatDoesNotExist {}"
job.should_receive(:attempt_to_load).with('JobThatDoesNotExist').and_return(true)
lambda { job.payload_object.perform }.should raise_error(Delayed::Backend::DeserializationError)

it "should autoload classes that are unknown at runtime" do
job = @backend.new :handler => "--- !ruby/object:Autoloaded::Clazz {}"
lambda { job.payload_object }.should_not raise_error(Delayed::Backend::DeserializationError)
end

it "should try include the namespace when loading unknown structs" do
job = @backend.new :handler => "--- !ruby/struct:Delayed::JobThatDoesNotExist {}"
job.should_receive(:attempt_to_load).with('Delayed::JobThatDoesNotExist').and_return(true)
lambda { job.payload_object.perform }.should raise_error(Delayed::Backend::DeserializationError)
it "should autoload structs that are unknown at runtime" do
job = @backend.new :handler => "--- !ruby/struct:Autoloaded::Struct {}"
lambda { job.payload_object }.should_not raise_error(Delayed::Backend::DeserializationError)
end
end

Expand Down
3 changes: 3 additions & 0 deletions spec/spec_helper.rb
Expand Up @@ -26,3 +26,6 @@
end

Delayed::Worker.backend = BACKENDS.first

# Add this directory so the ActiveSupport autoloading works
ActiveSupport::Dependencies.load_paths << File.dirname(__FILE__)

0 comments on commit 4c53af8

Please sign in to comment.