require 'backburner/async_proxy' module Backburner module Performable def self.included(base) base.send(:include, InstanceMethods) base.send(:include, Backburner::Queue) base.extend ClassMethods end module InstanceMethods # Return proxy object to enqueue jobs for object # Options: `pri` (priority), `delay` (delay in secs), `ttr` (time to respond), `queue` (queue name) # @example # @model.async(:pri => 1000).do_something("foo") # def async(opts={}) Backburner::AsyncProxy.new(self.class, self.id, opts) end end # InstanceMethods module ClassMethods # Return proxy object to enqueue jobs for object # Options: `pri` (priority), `delay` (delay in secs), `ttr` (time to respond), `queue` (queue name) # @example # Model.async(:ttr => 300).do_something("foo") def async(opts={}) Backburner::AsyncProxy.new(self, nil, opts) end # Defines perform method for job processing # @example # perform(55, :do_something, "foo", "bar") def perform(id, method, *args) if id # instance find(id).send(method, *args) else # class method send(method, *args) end end # perform # Always handle an instance method asynchronously # @example # User.handle_asynchronously :send_welcome_email, queue: 'send-mail', delay: 10 def handle_asynchronously(method, opts={}) Backburner::Performable.handle_asynchronously(self, method, opts) end # Always handle a class method asynchronously # @example # User.handle_static_asynchronously :update_recent_visitors, ttr: 300 def handle_static_asynchronously(method, opts={}) Backburner::Performable.handle_static_asynchronously(self, method, opts) end end # ClassMethods # Make all calls to an instance method asynchronous. The given opts will be passed # to the async method. # @example # Backburner::Performable.handle_asynchronously(MyObject, :long_task, queue: 'long-tasks') # NB: The method called on the async proxy will be ""#{method}_without_async". This # will also be what's given to the Worker.enqueue method so your workers need # to know about that. It shouldn't be a problem unless the producer and consumer are # from different codebases (or anywhere they don't both call the handle_asynchronously # method when booting up) def self.handle_asynchronously(klass, method, opts={}) _handle_asynchronously(klass, klass, method, opts) end # Make all calls to a class method asynchronous. The given opts will be passed # to the async method. Please see the NB on #handle_asynchronously def self.handle_static_asynchronously(klass, method, opts={}) _handle_asynchronously(klass, klass.singleton_class, method, opts) end def self._handle_asynchronously(klass, klass_eval_scope, method, opts={}) aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1 with_async_name = :"#{aliased_method}_with_async#{punctuation}" without_async_name = :"#{aliased_method}_without_async#{punctuation}" klass.send(:include, Performable) unless included_modules.include?(Performable) klass_eval_scope.class_eval do define_method with_async_name do |*args| async(opts).__send__ without_async_name, *args end alias_method without_async_name, method.to_sym alias_method method.to_sym, with_async_name end end private_class_method :_handle_asynchronously end # Performable end # Backburner