Skip to content

Commit

Permalink
Merge pull request resque#117 from yaauie/schedule-hooks
Browse files Browse the repository at this point in the history
add support for before_schedule and after_schedule hooks
  • Loading branch information
bvandenbos committed Nov 2, 2011
2 parents 581edf1 + 44fe11d commit 8d0176f
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 1 deletion.
6 changes: 6 additions & 0 deletions README.markdown
Expand Up @@ -128,6 +128,12 @@ down when a particular job is supposed to be queue, they will simply "catch up"
once they are started again. Jobs are guaranteed to run (provided they make it
into the delayed queue) after their given queue_at time has passed.

Similar to `before_enqueue` and `after_enqueue` hooks provided in Resque
(>= 1.19.1), your jobs can specify one or more `before_schedule` and
`after_schedule` hooks, to be run before or after scheduling. If *any* of your
`before_schedule` hooks returns `false`, the job will *not* be scheduled and
your `after_schedule` hooks will *not* be run.

One other thing to note is that insertion into the delayed queue is O(log(n))
since the jobs are stored in a redis sorted set (zset). I can't imagine this
being an issue for someone since redis is stupidly fast even at log(n), but full
Expand Down
19 changes: 18 additions & 1 deletion lib/resque_scheduler.rb
Expand Up @@ -102,14 +102,23 @@ def remove_schedule(name)
# sit in the schedule list.
def enqueue_at(timestamp, klass, *args)
validate_job!(klass)
delayed_push(timestamp, job_to_hash(klass, args))
enqueue_at_with_queue( queue_from_class(klass), timestamp, klass, *args)
end

# Identical to +enqueue_at+, except you can also specify
# a queue in which the job will be placed after the
# timestamp has passed.
def enqueue_at_with_queue(queue, timestamp, klass, *args)
before_hooks = before_schedule_hooks(klass).collect do |hook|
klass.send(hook,*args)
end
return false if before_hooks.any? { |result| result == false }

delayed_push(timestamp, job_to_hash_with_queue(queue, klass, args))

after_schedule_hooks(klass).collect do |hook|
klass.send(hook,*args)
end
end

# Identical to enqueue_at but takes number_of_seconds_from_now
Expand Down Expand Up @@ -240,6 +249,14 @@ def validate_job!(klass)
end
end

def before_schedule_hooks(klass)
klass.methods.grep(/^before_schedule/).sort
end

def after_schedule_hooks(klass)
klass.methods.grep(/^after_schedule/).sort
end

end

Resque.extend ResqueScheduler
Expand Down
52 changes: 52 additions & 0 deletions test/scheduler_hooks_test.rb
@@ -0,0 +1,52 @@
require File.dirname(__FILE__) + '/test_helper'

context "scheduling jobs with hooks" do
class JobThatCannotBeScheduledWithoutArguments < Resque::Job
@queue = :job_that_cannot_be_scheduled_without_arguments
def self.perform(*x);end
def self.before_schedule_return_nil_if_arguments_not_supplied(*args)
counters[:before_schedule] += 1
return false if args.empty?
end

def self.after_schedule_do_something(*args)
counters[:after_schedule] += 1
end

class << self
def counters
@counters ||= Hash.new{|h,k| h[k]=0}
end
def clean
counters.clear
self
end
end
end

setup do
Resque::Scheduler.dynamic = false
Resque.redis.del(:schedules)
Resque.redis.del(:schedules_changed)
Resque::Scheduler.mute = true
Resque::Scheduler.clear_schedule!
Resque::Scheduler.send(:class_variable_set, :@@scheduled_jobs, {})
end

test "before_schedule hook that does not return false should not block" do
enqueue_time = Time.now + 12
Resque.enqueue_at(enqueue_time, JobThatCannotBeScheduledWithoutArguments.clean, :foo)
assert_equal(1, Resque.delayed_timestamp_size(enqueue_time.to_i), "delayed queue should have one entry now")
assert_equal(1, JobThatCannotBeScheduledWithoutArguments.counters[:before_schedule], 'before_schedule was not run')
assert_equal(1, JobThatCannotBeScheduledWithoutArguments.counters[:after_schedule], 'after_schedule was not run')
end

test "before_schedule hook that returns false should block" do
enqueue_time = Time.now + 60
assert_equal(0, JobThatCannotBeScheduledWithoutArguments.clean.counters[:before_schedule], 'before_schedule should be zero')
Resque.enqueue_at(enqueue_time, JobThatCannotBeScheduledWithoutArguments.clean)
assert_equal(0, Resque.delayed_timestamp_size(enqueue_time.to_i), "job should not have been put in queue")
assert_equal(1, JobThatCannotBeScheduledWithoutArguments.counters[:before_schedule], 'before_schedule was not run')
assert_equal(0, JobThatCannotBeScheduledWithoutArguments.counters[:after_schedule], 'after_schedule was run')
end
end

0 comments on commit 8d0176f

Please sign in to comment.