YAML dump Class and ActiveRecord::Base objects natively, refs #1

This fixes a long-standing issue with not being able to properly
serialize AR objects that are in arrays or other data structures. Now
that we're using native YAML methods for doing the serialization, rather
than dumping specially-formatted "LOAD;N" strings, it works properly.

I've left the load_for_delayed_job stuff in for now, for backwards
compatibility with jobs already in the queue.

Change-Id: I109f364b91c4becc8fc17ea1d9f041eb3c18281f
Reviewed-on: https://gerrit.instructure.com/2389
Tested-by: Hudson <hudson@instructure.com>
Reviewed-by: Bracken Mosbacker <bracken@instructure.com>
This commit is contained in:
Brian Palmer 2011-02-22 13:21:03 -07:00
parent 2f9e0088fc
commit b86fb7fd83
4 changed files with 38 additions and 29 deletions

View File

@ -8,13 +8,6 @@ class ActiveRecord::Base
super
end
end
def dump_for_delayed_job
if id.nil?
raise("Can't serialize unsaved ActiveRecord object for delayed job: #{self.inspect}")
end
"#{self.class};#{id}"
end
end
module Delayed

View File

@ -72,13 +72,13 @@ module Delayed
def reschedule_at
new_time = self.class.db_time_now + (attempts ** 4) + 5
if payload_object.respond_to?(:reschedule_at)
begin
begin
if payload_object.respond_to?(:reschedule_at)
new_time = payload_object.reschedule_at(
self.class.db_time_now, attempts)
rescue
# TODO: just swallow errors from reschedule_at ?
end
rescue
# TODO: just swallow errors from reschedule_at ?
end
new_time
end

View File

@ -1,10 +1,32 @@
YAML.add_ruby_type("object:Class") do |type, val|
val.constantize
end
class Class
def to_yaml(opts = {})
YAML.quick_emit(self.object_id, opts) do |out|
out.scalar(taguri, name)
end
end
def load_for_delayed_job(arg)
self
end
end
def dump_for_delayed_job
name
class ActiveRecord::Base
yaml_as "tag:ruby.yaml.org,2002:ActiveRecord"
def to_yaml(opts = {})
YAML.quick_emit(self.object_id, opts) do |out|
out.scalar(taguri, id.to_s)
end
end
def self.yaml_new(klass, tag, val)
klass.find(val)
rescue ActiveRecord::RecordNotFound
raise Delayed::Backend::DeserializationError, "Couldn't find #{klass} with id #{val.inspect}"
end
end
@ -31,16 +53,14 @@ module Delayed
self.args = args.map { |a| dump(a) }
self.method = method.to_sym
if object.is_a?(Module)
self.tag = "#{object.name}.#{method}"
else
self.tag = "#{object.class}##{method}"
end
self.tag = display_name
end
def display_name
if STRING_FORMAT === object
"#{$1}#{$2 ? '#' : '.'}#{method}"
elsif object.is_a?(Module)
"#{object}.#{method}"
else
"#{object.class}##{method}"
end
@ -68,11 +88,7 @@ module Delayed
end
def dump(obj)
if obj.respond_to?(:dump_for_delayed_job)
"LOAD;#{obj.dump_for_delayed_job}"
else
obj
end
obj
end
end
end

View File

@ -8,18 +8,18 @@ end
describe Delayed::PerformableMethod do
it "should NOT FREAKING ignore ActiveRecord::RecordNotFound errors because they are NOT ALWAYS permanent" do
it "should not ignore ActiveRecord::RecordNotFound errors because they are not always permanent" do
story = Story.create :text => 'Once upon...'
p = Delayed::PerformableMethod.new(story, :tell, [])
story.destroy
lambda { p.perform }.should raise_error
lambda { YAML.load(p.to_yaml) }.should raise_error
end
it "should store the object as string if its an active record" do
it "should store the object using native YAML even if its an active record" do
story = Story.create :text => 'Once upon...'
p = Delayed::PerformableMethod.new(story, :tell, [])
p.class.should == Delayed::PerformableMethod
p.object.should == "LOAD;Story;#{story.id}"
p.object.should == story
p.method.should == :tell
p.args.should == []
p.perform.should == 'Once upon...'
@ -30,13 +30,13 @@ describe Delayed::PerformableMethod do
lambda { p.send(:load, p.object) }.should_not raise_error
end
it "should store arguments as string if they are active record objects" do
it "should store arguments as native YAML if they are active record objects" do
story = Story.create :text => 'Once upon...'
reader = StoryReader.new
p = Delayed::PerformableMethod.new(reader, :read, [story])
p.class.should == Delayed::PerformableMethod
p.method.should == :read
p.args.should == ["LOAD;Story;#{story.id}"]
p.args.should == [story]
p.perform.should == 'Epilog: Once upon...'
end