Created
November 5, 2010 10:55
-
-
Save armstrjare/663967 to your computer and use it in GitHub Desktop.
UNTESTED... draft
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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