Skip to content

Instantly share code, notes, and snippets.

@armstrjare
Created November 5, 2010 10:55
Show Gist options
  • Save armstrjare/663967 to your computer and use it in GitHub Desktop.
Save armstrjare/663967 to your computer and use it in GitHub Desktop.
UNTESTED... draft
# Provide a simple API for performing methods asynchronously on objects.
#
# Two approaches:
# 1) like DelayedJob's "delay" method
# my_obj.async.some_blocking_method(1, 2)
#
# 2) like DelayedJob's handle_asynchronously helper
# class Blah < ActiveRecord::Base
# extend ResqueAsync
# be_async :some_blocking_method
# end
#
# It's also smart enough to not make nested Resque jobs, so calling an async method during
# running of a Job will just pass-through and run normally.
#
# Author: Jared Armstrong
module ResqueAsync
mattr_accessor :allow_nested_async, :performing_job
# Allow ResqueAsync to create Resque jobs from during the running of a job?
self.allow_nested_async = false
# This exception is thrown when an instance method is attempted to be queued up, but there is
# no serializer defined for the class that object is an instance of.
NoSerializer = Class.new(StandardError)
# Extend to provide class-level async functinality
def self.extended(base)
# Provide class specific methods
base.send :extend, ClassMethods
# Provide async helper for both Class and instances
base.send :include, AsyncHelper
base.send :extend, AsyncHelper
if base <= ActiveRecord::Base
base.send :extend, ActiveRecordSerializer
end
end
# Same as extending
def self.included(base)
base.send :extend, self
end
module AsyncHelper
# Return a proxy which automatically assigns any method calls onto the Resque queue.
def async
AsyncProxy.new(self)
end
end
module ClassMethods
# Declare that methods should be handled asynchronously automatically.
def be_async(*methods)
methods.each do |method|
wrapped_method = method.to_s
# Need to move special method characters to end
special = if ["?", "!"].include?(wrapped_method[-1])
wrapped_method.slice!(-1)
else
""
end
define_method "#{wrapped_method}_with_async#{special}" do |*args|
self.async.send("#{wrapped_method}_without_async#{special}", *args)
end
alias_method_chain method, :async
end
end
# Perform an asynchronous method call.
#
# If target is nil, the target of the method invocation is the class/module.
#
# If target is an array, that is passed as arguments to the deserializer, and the
# the resulting object will be the target of the method invocation.
def perform(target, method, *args)
begin
ResqueAsync.performing_job = true
target = if target
resque_async_deserialize(*target)
else
self
end
target.send(method, *args)
ensure
ResqueAsync.performing_job = false
end
end
# Enqueue a method invocation onto the Resque queue associated with the
# class.
#
# "target" is the object that is the target of the method call. If it is an instance
# of the Resque "performer" class, will attempt to serialize the object instance
# so that it can be deserialized by when performing the Job.
def enqueue(target, method, *args)
# If we're currently processing a Resque job, and we don't allow nested
# async calls, just run the method immediately.
if ResqueAsync.performing_job && !ResqueAsync.allow_nested_async
return (target || self).send(method, *args)
end
target_opts = if target.instance_of?(self)
Array.wrap(resque_async_serialize(target))
else
nil
end
Resque.enqueue(self, target_opts, method, *args)
end
# Deserializes an array of arguments into an object instance to be the target
# of an async method invocation
def resque_async_deserialize(*args)
raise NoSerializer
end
# Serializes an object instance into a lists of arguments to be passed to the
# deserializer.
def resque_async_serialize(instance)
raise NoSerializer
end
end
# Proxy that enqueues any message sent to the target into a the class' Resque queue.
class AsyncProxy
def initialize(target)
@target = target
end
def method_missing(method, *args)
(@target.is_a?(Class) ? @target : @target.class).enqueue(@target, method, *args)
end
end
# Serializer methods for ActiveRecord objects.
module ActiveRecordSerializer
def resque_async_deserialize(id)
find(id)
end
def resque_async_serialize(instance)
[instance.id]
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment