I created a simplistic achievements system and wanted to introduce delayed_job (2.1.4) to take care of the processing. However, the handler column in the delayed_jobs table is always nil, which results in the last_error text: Job failed to load: instance of IO needed. Handler nil
Here is my setup:
Achievement Observer
class AchievementObserver < ActiveRecord::Observer
observe User, Comment, ...
def after_create(record)
# initiate delayed job to check conditions
Delayed::Job.enqueue(TrophyJob.new(record.id, record.class.name))
end
...
end
Trophy Job
class TrophyJob < Struct.new(:record_id, :record_type)
def perform
case record_type
when "User"
UserProfileCompleteTrophy.progress(User.find(record_id)) # complete your profile settings
NewsletterReceiverTrophy.progress(User.find(record_id)) # sign up for the newsletter
when "Comment"
CommentsAuthoredTrophy.progress(Comment.find(record_id)) # create x comments
...
end
end
end
The entry in the delayed_jobs table gets created. However, the handler is always NULL. I've already tried various things (passed through complete objects before, now went for id + classname as described here: Weird exception with delayed_job; tried xxxTrophy.delay.progress(...) in the observer; etc.) - all without luck.
I also have
require 'yaml'
YAML::ENGINE.yamler= 'syck'
in my boot.rb.
One thing worth mentioning: Although the last_error text gets filled by delayed_job, the attempts and failed_at columns remain NULL.
What am I missing?
UPDATE
I verified that serialization works as one would expect:
ruby-1.9.2-p290 :004 > TrophyJob.new(1, "User").to_yaml
=> "--- !ruby/struct:TrophyJob \nrecord_id: 1\nrecord_type: User\n"
Found the solution: my problem was caused by mass-assignment protection. I have an initializer to protect against mass-assignment:
# SECURITY: protect against mass assignment vulnerabilities
# enforces explicitly setting attributes accessible in models (whitelisting)
ActiveRecord::Base.send(:attr_accessible, nil)
This prevented delayed_job to access the handler field! Not sure if this can be considered a bug in DJ. Here is the initializer code that solved my problem:
# Imortant: delayed job requires some attributes to be accessible - make sure they are
Delayed::Job.attr_accessible :priority, :payload_object, :handler, :run_at, :failed_at