aboutsummaryrefslogtreecommitdiffstats
path: root/activejob/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activejob/lib')
-rw-r--r--activejob/lib/active_job.rb33
-rw-r--r--activejob/lib/active_job/arguments.rb62
-rw-r--r--activejob/lib/active_job/base.rb22
-rw-r--r--activejob/lib/active_job/callbacks.rb144
-rw-r--r--activejob/lib/active_job/enqueuing.rb71
-rw-r--r--activejob/lib/active_job/execution.rb33
-rw-r--r--activejob/lib/active_job/gem_version.rb15
-rw-r--r--activejob/lib/active_job/identifier.rb15
-rw-r--r--activejob/lib/active_job/logging.rb90
-rw-r--r--activejob/lib/active_job/queue_adapter.rb24
-rw-r--r--activejob/lib/active_job/queue_adapters/backburner_adapter.rb25
-rw-r--r--activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb23
-rw-r--r--activejob/lib/active_job/queue_adapters/inline_adapter.rb15
-rw-r--r--activejob/lib/active_job/queue_adapters/qu_adapter.rb30
-rw-r--r--activejob/lib/active_job/queue_adapters/que_adapter.rb23
-rw-r--r--activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb23
-rw-r--r--activejob/lib/active_job/queue_adapters/resque_adapter.rb41
-rw-r--r--activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb35
-rw-r--r--activejob/lib/active_job/queue_adapters/sneakers_adapter.rb34
-rw-r--r--activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb25
-rw-r--r--activejob/lib/active_job/queue_name.rb18
-rw-r--r--activejob/lib/active_job/railtie.rb23
-rw-r--r--activejob/lib/active_job/version.rb8
-rw-r--r--activejob/lib/rails/generators/job/job_generator.rb22
-rw-r--r--activejob/lib/rails/generators/job/templates/job.rb9
25 files changed, 863 insertions, 0 deletions
diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb
new file mode 100644
index 0000000000..ef92406725
--- /dev/null
+++ b/activejob/lib/active_job.rb
@@ -0,0 +1,33 @@
+#--
+# Copyright (c) 2014 David Heinemeier Hansson
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#++
+
+require 'active_support'
+require 'active_support/rails'
+require 'active_job/version'
+require 'global_id'
+
+module ActiveJob
+ extend ActiveSupport::Autoload
+
+ autoload :Base
+end \ No newline at end of file
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb
new file mode 100644
index 0000000000..369e716912
--- /dev/null
+++ b/activejob/lib/active_job/arguments.rb
@@ -0,0 +1,62 @@
+module ActiveJob
+ class DeserializationError < StandardError
+ attr_reader :original_exception
+
+ def initialize(e)
+ super ("Error while trying to deserialize arguments: #{e.message}")
+ @original_exception = e
+ set_backtrace e.backtrace
+ end
+ end
+
+ module Arguments
+ extend self
+ TYPE_WHITELIST = [ NilClass, Fixnum, Float, String, TrueClass, FalseClass, Bignum ]
+
+ def serialize(arguments)
+ arguments.map { |argument| serialize_argument(argument) }
+ end
+
+ def deserialize(arguments)
+ arguments.map { |argument| deserialize_argument(argument) }
+ end
+
+ private
+ def serialize_argument(argument)
+ case argument
+ when GlobalID::Identification
+ argument.global_id.to_s
+ when *TYPE_WHITELIST
+ argument
+ when Array
+ serialize(argument)
+ when Hash
+ Hash[ argument.map { |key, value| [ serialize_hash_key(key), serialize_argument(value) ] } ]
+ else
+ raise "Unsupported argument type: #{argument.class.name}"
+ end
+ end
+
+ def deserialize_argument(argument)
+ case argument
+ when Array
+ deserialize(argument)
+ when Hash
+ Hash[ argument.map { |key, value| [ key, deserialize_argument(value) ] } ].with_indifferent_access
+ else
+ GlobalID::Locator.locate(argument) || argument
+ end
+ rescue => e
+ raise DeserializationError.new(e)
+ end
+
+ def serialize_hash_key(key)
+ case key
+ when String, Symbol
+ key.to_s
+ else
+ raise "Unsupported hash key type: #{key.class.name}"
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/base.rb b/activejob/lib/active_job/base.rb
new file mode 100644
index 0000000000..d5ba253016
--- /dev/null
+++ b/activejob/lib/active_job/base.rb
@@ -0,0 +1,22 @@
+require 'active_job/queue_adapter'
+require 'active_job/queue_name'
+require 'active_job/enqueuing'
+require 'active_job/execution'
+require 'active_job/callbacks'
+require 'active_job/identifier'
+require 'active_job/logging'
+
+module ActiveJob
+ class Base
+ extend QueueAdapter
+
+ include QueueName
+ include Enqueuing
+ include Execution
+ include Callbacks
+ include Identifier
+ include Logging
+
+ ActiveSupport.run_load_hooks(:active_job, self)
+ end
+end
diff --git a/activejob/lib/active_job/callbacks.rb b/activejob/lib/active_job/callbacks.rb
new file mode 100644
index 0000000000..af92031bc9
--- /dev/null
+++ b/activejob/lib/active_job/callbacks.rb
@@ -0,0 +1,144 @@
+require 'active_support/callbacks'
+
+module ActiveJob
+ # = Active Job Callbacks
+ #
+ # Active Job provides hooks during the lifecycle of a job. Callbacks allows you to trigger
+ # logic during the lifecycle of a job. Available callbacks:
+ #
+ # * <tt>before_enqueue</tt>
+ # * <tt>around_enqueue</tt>
+ # * <tt>after_enqueue</tt>
+ # * <tt>before_perform</tt>
+ # * <tt>around_perform</tt>
+ # * <tt>after_perform</tt>
+ #
+ module Callbacks
+ extend ActiveSupport::Concern
+ include ActiveSupport::Callbacks
+
+ included do
+ define_callbacks :perform
+ define_callbacks :enqueue
+ end
+
+ module ClassMethods
+ # Defines a callback that will get called right before the
+ # job's perform method is executed.
+ #
+ # class VideoProcessJob < ActiveJob::Base
+ # queue_as :default
+ #
+ # before_perform do |job|
+ # UserMailer.notify_video_started_processing(job.arguments.first)
+ # end
+ #
+ # def perform(video_id)
+ # Video.find(video_id).process
+ # end
+ # end
+ #
+ def before_perform(*filters, &blk)
+ set_callback(:perform, :before, *filters, &blk)
+ end
+
+ # Defines a callback that will get called right after the
+ # job's perform method has finished.
+ #
+ # class VideoProcessJob < ActiveJob::Base
+ # queue_as :default
+ #
+ # after_perform do |job|
+ # UserMailer.notify_video_processed(job.arguments.first)
+ # end
+ #
+ # def perform(video_id)
+ # Video.find(video_id).process
+ # end
+ # end
+ #
+ def after_perform(*filters, &blk)
+ set_callback(:perform, :after, *filters, &blk)
+ end
+
+ # Defines a callback that will get called around the job's perform method.
+ #
+ # class VideoProcessJob < ActiveJob::Base
+ # queue_as :default
+ #
+ # around_perform do |job, block|
+ # UserMailer.notify_video_started_processing(job.arguments.first)
+ # block.call
+ # UserMailer.notify_video_processed(job.arguments.first)
+ # end
+ #
+ # def perform(video_id)
+ # Video.find(video_id).process
+ # end
+ # end
+ #
+ def around_perform(*filters, &blk)
+ set_callback(:perform, :around, *filters, &blk)
+ end
+
+ # Defines a callback that will get called right before the
+ # job is enqueued.
+ #
+ # class VideoProcessJob < ActiveJob::Base
+ # queue_as :default
+ #
+ # before_enqueue do |job|
+ # $statsd.increment "enqueue-video-job.try"
+ # end
+ #
+ # def perform(video_id)
+ # Video.find(video_id).process
+ # end
+ # end
+ #
+ def before_enqueue(*filters, &blk)
+ set_callback(:enqueue, :before, *filters, &blk)
+ end
+
+ # Defines a callback that will get called right after the
+ # job is enqueued.
+ #
+ # class VideoProcessJob < ActiveJob::Base
+ # queue_as :default
+ #
+ # after_enqueue do |job|
+ # $statsd.increment "enqueue-video-job.success"
+ # end
+ #
+ # def perform(video_id)
+ # Video.find(video_id).process
+ # end
+ # end
+ #
+ def after_enqueue(*filters, &blk)
+ set_callback(:enqueue, :after, *filters, &blk)
+ end
+
+ # Defines a callback that will get called before and after the
+ # job is enqueued.
+ #
+ # class VideoProcessJob < ActiveJob::Base
+ # queue_as :default
+ #
+ # around_enqueue do |job, block|
+ # $statsd.time "video-job.process" do
+ # block.call
+ # end
+ # end
+ #
+ # def perform(video_id)
+ # Video.find(video_id).process
+ # end
+ # end
+ #
+ def around_enqueue(*filters, &blk)
+ set_callback(:enqueue, :around, *filters, &blk)
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/enqueuing.rb b/activejob/lib/active_job/enqueuing.rb
new file mode 100644
index 0000000000..3d00d51867
--- /dev/null
+++ b/activejob/lib/active_job/enqueuing.rb
@@ -0,0 +1,71 @@
+require 'active_job/arguments'
+
+module ActiveJob
+ module Enqueuing
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ # Push a job onto the queue. The arguments must be legal JSON types
+ # (string, int, float, nil, true, false, hash or array) or
+ # GlobalID::Identification instances. Arbitrary Ruby objects
+ # are not supported.
+ #
+ # Returns an instance of the job class queued with args available in
+ # Job#arguments.
+ def enqueue(*args)
+ new(args).tap do |job|
+ job.run_callbacks :enqueue do
+ queue_adapter.enqueue self, job.job_id, *Arguments.serialize(args)
+ end
+ end
+ end
+
+ # Enqueue a job to be performed at +interval+ from now.
+ #
+ # enqueue_in(1.week, "mike")
+ #
+ # Returns an instance of the job class queued with args available in
+ # Job#arguments and the timestamp in Job#enqueue_at.
+ def enqueue_in(interval, *args)
+ enqueue_at interval.seconds.from_now, *args
+ end
+
+ # Enqueue a job to be performed at an explicit point in time.
+ #
+ # enqueue_at(Date.tomorrow.midnight, "mike")
+ #
+ # Returns an instance of the job class queued with args available in
+ # Job#arguments and the timestamp in Job#enqueue_at.
+ def enqueue_at(timestamp, *args)
+ new(args).tap do |job|
+ job.enqueued_at = timestamp
+
+ job.run_callbacks :enqueue do
+ queue_adapter.enqueue_at self, timestamp.to_f, job.job_id, *Arguments.serialize(args)
+ end
+ end
+ end
+ end
+
+ included do
+ attr_accessor :arguments
+ attr_accessor :enqueued_at
+ end
+
+ def initialize(arguments = nil)
+ @arguments = arguments
+ end
+
+ def retry_now
+ self.class.enqueue(*arguments)
+ end
+
+ def retry_in(interval)
+ self.class.enqueue_in interval, *arguments
+ end
+
+ def retry_at(timestamp)
+ self.class.enqueue_at timestamp, *arguments
+ end
+ end
+end
diff --git a/activejob/lib/active_job/execution.rb b/activejob/lib/active_job/execution.rb
new file mode 100644
index 0000000000..0e7b5bdd72
--- /dev/null
+++ b/activejob/lib/active_job/execution.rb
@@ -0,0 +1,33 @@
+require 'active_support/rescuable'
+require 'active_job/arguments'
+
+module ActiveJob
+ module Execution
+ extend ActiveSupport::Concern
+
+ included do
+ include ActiveSupport::Rescuable
+ end
+
+ def execute(job_id, *serialized_args)
+ self.job_id = job_id
+ self.arguments = deserialize_arguments(serialized_args)
+
+ run_callbacks :perform do
+ perform(*arguments)
+ end
+ rescue => exception
+ rescue_with_handler(exception) || raise(exception)
+ end
+
+ def perform(*)
+ fail NotImplementedError
+ end
+
+ private
+ def deserialize_arguments(serialized_args)
+ Arguments.deserialize(serialized_args)
+ end
+
+ end
+end
diff --git a/activejob/lib/active_job/gem_version.rb b/activejob/lib/active_job/gem_version.rb
new file mode 100644
index 0000000000..c166020b28
--- /dev/null
+++ b/activejob/lib/active_job/gem_version.rb
@@ -0,0 +1,15 @@
+module ActiveJob
+ # Returns the version of the currently loaded ActiveJob as a <tt>Gem::Version</tt>
+ def self.gem_version
+ Gem::Version.new VERSION::STRING
+ end
+
+ module VERSION
+ MAJOR = 4
+ MINOR = 2
+ TINY = 0
+ PRE = "alpha"
+
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
+ end
+end
diff --git a/activejob/lib/active_job/identifier.rb b/activejob/lib/active_job/identifier.rb
new file mode 100644
index 0000000000..c7f522087d
--- /dev/null
+++ b/activejob/lib/active_job/identifier.rb
@@ -0,0 +1,15 @@
+require 'active_job/arguments'
+
+module ActiveJob
+ module Identifier
+ extend ActiveSupport::Concern
+
+ included do
+ attr_writer :job_id
+ end
+
+ def job_id
+ @job_id ||= SecureRandom.uuid
+ end
+ end
+end
diff --git a/activejob/lib/active_job/logging.rb b/activejob/lib/active_job/logging.rb
new file mode 100644
index 0000000000..d9e544acf5
--- /dev/null
+++ b/activejob/lib/active_job/logging.rb
@@ -0,0 +1,90 @@
+require 'active_support/core_ext/string/filters'
+require 'active_support/tagged_logging'
+require 'active_support/logger'
+
+module ActiveJob
+ module Logging
+ extend ActiveSupport::Concern
+
+ included do
+ cattr_accessor(:logger) { ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT)) }
+
+ around_enqueue do |_, block, _|
+ tag_logger do
+ block.call
+ end
+ end
+
+ around_perform do |job, block, _|
+ tag_logger(job.class.name, job.job_id) do
+ payload = {adapter: job.class.queue_adapter, job: job.class, args: job.arguments}
+ ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup)
+ ActiveSupport::Notifications.instrument("perform.active_job", payload) do
+ block.call
+ end
+ end
+ end
+
+ before_enqueue do |job|
+ if job.enqueued_at
+ ActiveSupport::Notifications.instrument "enqueue_at.active_job",
+ adapter: job.class.queue_adapter, job: job.class, job_id: job.job_id, args: job.arguments, timestamp: job.enqueued_at
+ else
+ ActiveSupport::Notifications.instrument "enqueue.active_job",
+ adapter: job.class.queue_adapter, job: job.class, job_id: job.job_id, args: job.arguments
+ end
+ end
+ end
+
+ private
+ def tag_logger(*tags)
+ if logger.respond_to?(:tagged)
+ tags.unshift "ActiveJob" unless logger_tagged_by_active_job?
+ ActiveJob::Base.logger.tagged(*tags){ yield }
+ else
+ yield
+ end
+ end
+
+ def logger_tagged_by_active_job?
+ logger.formatter.current_tags.include?("ActiveJob")
+ end
+
+ class LogSubscriber < ActiveSupport::LogSubscriber
+ def enqueue(event)
+ info "Enqueued #{event.payload[:job].name} (Job ID: #{event.payload[:job_id]}) to #{queue_name(event)}" + args_info(event)
+ end
+
+ def enqueue_at(event)
+ info "Enqueued #{event.payload[:job].name} (Job ID: #{event.payload[:job_id]}) to #{queue_name(event)} at #{enqueued_at(event)}" + args_info(event)
+ end
+
+ def perform_start(event)
+ info "Performing #{event.payload[:job].name} from #{queue_name(event)}" + args_info(event)
+ end
+
+ def perform(event)
+ info "Performed #{event.payload[:job].name} from #{queue_name(event)} in #{event.duration.round(2).to_s}ms"
+ end
+
+ private
+ def queue_name(event)
+ event.payload[:adapter].name.demodulize.remove('Adapter') + "(#{event.payload[:job].queue_name})"
+ end
+
+ def args_info(event)
+ event.payload[:args].any? ? " with arguments: #{event.payload[:args].map(&:inspect).join(", ")}" : ""
+ end
+
+ def enqueued_at(event)
+ Time.at(event.payload[:timestamp]).utc
+ end
+
+ def logger
+ ActiveJob::Base.logger
+ end
+ end
+ end
+end
+
+ActiveJob::Logging::LogSubscriber.attach_to :active_job
diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb
new file mode 100644
index 0000000000..8f2f8b86ea
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapter.rb
@@ -0,0 +1,24 @@
+require 'active_job/queue_adapters/inline_adapter'
+require 'active_support/core_ext/string/inflections'
+
+module ActiveJob
+ module QueueAdapter
+ mattr_reader(:queue_adapter) { ActiveJob::QueueAdapters::InlineAdapter }
+
+ def queue_adapter=(name_or_adapter)
+ @@queue_adapter = \
+ case name_or_adapter
+ when Symbol, String
+ load_adapter(name_or_adapter)
+ when Class
+ name_or_adapter
+ end
+ end
+
+ private
+ def load_adapter(name)
+ require "active_job/queue_adapters/#{name}_adapter"
+ "ActiveJob::QueueAdapters::#{name.to_s.camelize}Adapter".constantize
+ end
+ end
+end \ No newline at end of file
diff --git a/activejob/lib/active_job/queue_adapters/backburner_adapter.rb b/activejob/lib/active_job/queue_adapters/backburner_adapter.rb
new file mode 100644
index 0000000000..7a6032e56b
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/backburner_adapter.rb
@@ -0,0 +1,25 @@
+require 'backburner'
+
+module ActiveJob
+ module QueueAdapters
+ class BackburnerAdapter
+ class << self
+ def enqueue(job, *args)
+ Backburner::Worker.enqueue JobWrapper, [ job.name, *args ], queue: job.queue_name
+ end
+
+ def enqueue_at(job, timestamp, *args)
+ raise NotImplementedError
+ end
+ end
+
+ class JobWrapper
+ class << self
+ def perform(job_name, *args)
+ job_name.constantize.new.execute *args
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb b/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
new file mode 100644
index 0000000000..bfeaa836d2
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
@@ -0,0 +1,23 @@
+require 'delayed_job'
+
+module ActiveJob
+ module QueueAdapters
+ class DelayedJobAdapter
+ class << self
+ def enqueue(job, *args)
+ JobWrapper.new.delay(queue: job.queue_name).perform(job, *args)
+ end
+
+ def enqueue_at(job, timestamp, *args)
+ JobWrapper.new.delay(queue: job.queue_name, run_at: Time.at(timestamp)).perform(job, *args)
+ end
+ end
+
+ class JobWrapper
+ def perform(job, *args)
+ job.new.execute *args
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/queue_adapters/inline_adapter.rb b/activejob/lib/active_job/queue_adapters/inline_adapter.rb
new file mode 100644
index 0000000000..50d14a321d
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/inline_adapter.rb
@@ -0,0 +1,15 @@
+module ActiveJob
+ module QueueAdapters
+ class InlineAdapter
+ class << self
+ def enqueue(job, *args)
+ job.new.execute *args
+ end
+
+ def enqueue_at(*)
+ raise NotImplementedError.new("Use a queueing backend to enqueue jobs in the future. Read more at https://github.com/rails/activejob")
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/queue_adapters/qu_adapter.rb b/activejob/lib/active_job/queue_adapters/qu_adapter.rb
new file mode 100644
index 0000000000..cdf4ae4ce9
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/qu_adapter.rb
@@ -0,0 +1,30 @@
+require 'qu'
+
+module ActiveJob
+ module QueueAdapters
+ class QuAdapter
+ class << self
+ def enqueue(job, *args)
+ Qu::Payload.new(klass: JobWrapper, args: [job.name, *args]).tap do |payload|
+ payload.instance_variable_set(:@queue, job.queue_name)
+ end.push
+ end
+
+ def enqueue_at(job, timestamp, *args)
+ raise NotImplementedError
+ end
+ end
+
+ class JobWrapper < Qu::Job
+ def initialize(job_name, *args)
+ @job = job_name.constantize
+ @args = args
+ end
+
+ def perform
+ @job.new.execute *@args
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/queue_adapters/que_adapter.rb b/activejob/lib/active_job/queue_adapters/que_adapter.rb
new file mode 100644
index 0000000000..15a607bcb6
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/que_adapter.rb
@@ -0,0 +1,23 @@
+require 'que'
+
+module ActiveJob
+ module QueueAdapters
+ class QueAdapter
+ class << self
+ def enqueue(job, *args)
+ JobWrapper.enqueue job.name, *args, queue: job.queue_name
+ end
+
+ def enqueue_at(job, timestamp, *args)
+ raise NotImplementedError
+ end
+ end
+
+ class JobWrapper < Que::Job
+ def run(job_name, *args)
+ job_name.constantize.new.execute *args
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb b/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb
new file mode 100644
index 0000000000..c61e0e30db
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb
@@ -0,0 +1,23 @@
+require 'queue_classic'
+
+module ActiveJob
+ module QueueAdapters
+ class QueueClassicAdapter
+ class << self
+ def enqueue(job, *args)
+ QC::Queue.new(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.name, *args)
+ end
+
+ def enqueue_at(job, timestamp, *args)
+ raise NotImplementedError
+ end
+ end
+
+ class JobWrapper
+ def self.perform(job_name, *args)
+ job_name.constantize.new.execute *args
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/queue_adapters/resque_adapter.rb b/activejob/lib/active_job/queue_adapters/resque_adapter.rb
new file mode 100644
index 0000000000..384aa0c4cc
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/resque_adapter.rb
@@ -0,0 +1,41 @@
+require 'resque'
+require 'active_support/core_ext/enumerable'
+require 'active_support/core_ext/array/access'
+
+begin
+ require 'resque-scheduler'
+rescue LoadError
+ begin
+ require 'resque_scheduler'
+ rescue LoadError
+ false
+ end
+end
+
+module ActiveJob
+ module QueueAdapters
+ class ResqueAdapter
+ class << self
+ def enqueue(job, *args)
+ Resque.enqueue_to job.queue_name, JobWrapper, job.name, *args
+ end
+
+ def enqueue_at(job, timestamp, *args)
+ unless Resque.respond_to?(:enqueue_at_with_queue)
+ raise NotImplementedError, "To be able to schedule jobs with Resque you need the " \
+ "resque-scheduler gem. Please add it to your Gemfile and run bundle install"
+ end
+ Resque.enqueue_at_with_queue job.queue_name, timestamp, JobWrapper, job.name, *args
+ end
+ end
+
+ class JobWrapper
+ class << self
+ def perform(job_name, *args)
+ job_name.constantize.new.execute *args
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
new file mode 100644
index 0000000000..f738a7d91c
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
@@ -0,0 +1,35 @@
+require 'sidekiq'
+
+module ActiveJob
+ module QueueAdapters
+ class SidekiqAdapter
+ class << self
+ def enqueue(job, *args)
+ #Sidekiq::Client does not support symbols as keys
+ Sidekiq::Client.push \
+ 'class' => JobWrapper,
+ 'queue' => job.queue_name,
+ 'args' => [ job, *args ],
+ 'retry' => true
+ end
+
+ def enqueue_at(job, timestamp, *args)
+ Sidekiq::Client.push \
+ 'class' => JobWrapper,
+ 'queue' => job.queue_name,
+ 'args' => [ job, *args ],
+ 'retry' => true,
+ 'at' => timestamp
+ end
+ end
+
+ class JobWrapper
+ include Sidekiq::Worker
+
+ def perform(job_name, *args)
+ job_name.constantize.new.execute *args
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb b/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb
new file mode 100644
index 0000000000..051a8c3bd7
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb
@@ -0,0 +1,34 @@
+require 'sneakers'
+require 'thread'
+
+module ActiveJob
+ module QueueAdapters
+ class SneakersAdapter
+ @monitor = Monitor.new
+
+ class << self
+ def enqueue(job, *args)
+ @monitor.synchronize do
+ JobWrapper.from_queue job.queue_name
+ JobWrapper.enqueue ActiveSupport::JSON.encode([ job.name, *args ])
+ end
+ end
+
+ def enqueue_at(job, timestamp, *args)
+ raise NotImplementedError
+ end
+ end
+
+ class JobWrapper
+ include Sneakers::Worker
+ from_queue 'active_jobs_default'
+
+ def work(msg)
+ job_name, *args = ActiveSupport::JSON.decode(msg)
+ job_name.constantize.new.execute *args
+ ack!
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb b/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb
new file mode 100644
index 0000000000..64b9c3ca15
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb
@@ -0,0 +1,25 @@
+require 'sucker_punch'
+
+module ActiveJob
+ module QueueAdapters
+ class SuckerPunchAdapter
+ class << self
+ def enqueue(job, *args)
+ JobWrapper.new.async.perform job, *args
+ end
+
+ def enqueue_at(job, timestamp, *args)
+ raise NotImplementedError
+ end
+ end
+
+ class JobWrapper
+ include SuckerPunch::Job
+
+ def perform(job, *args)
+ job.new.execute *args
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/queue_name.rb b/activejob/lib/active_job/queue_name.rb
new file mode 100644
index 0000000000..c2186d9fe9
--- /dev/null
+++ b/activejob/lib/active_job/queue_name.rb
@@ -0,0 +1,18 @@
+module ActiveJob
+ module QueueName
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ mattr_accessor(:default_queue_name) { "default" }
+
+ def queue_as(part_name)
+ self.queue_name = part_name.to_s.presence || default_queue_name
+ end
+ end
+
+ included do
+ class_attribute :queue_name
+ self.queue_name = default_queue_name
+ end
+ end
+end
diff --git a/activejob/lib/active_job/railtie.rb b/activejob/lib/active_job/railtie.rb
new file mode 100644
index 0000000000..6538ac1b30
--- /dev/null
+++ b/activejob/lib/active_job/railtie.rb
@@ -0,0 +1,23 @@
+require 'global_id/railtie'
+require 'active_job'
+
+module ActiveJob
+ # = Active Job Railtie
+ class Railtie < Rails::Railtie # :nodoc:
+ config.active_job = ActiveSupport::OrderedOptions.new
+
+ initializer 'active_job.logger' do
+ ActiveSupport.on_load(:active_job) { self.logger = ::Rails.logger }
+ end
+
+ initializer "active_job.set_configs" do |app|
+ options = app.config.active_job
+ options.queue_adapter ||= :inline
+
+ ActiveSupport.on_load(:active_job) do
+ options.each { |k,v| send("#{k}=", v) }
+ end
+ end
+
+ end
+end
diff --git a/activejob/lib/active_job/version.rb b/activejob/lib/active_job/version.rb
new file mode 100644
index 0000000000..7e646fa3c4
--- /dev/null
+++ b/activejob/lib/active_job/version.rb
@@ -0,0 +1,8 @@
+require_relative 'gem_version'
+
+module ActiveJob
+ # Returns the version of the currently loaded ActiveJob as a <tt>Gem::Version</tt>
+ def self.version
+ gem_version
+ end
+end
diff --git a/activejob/lib/rails/generators/job/job_generator.rb b/activejob/lib/rails/generators/job/job_generator.rb
new file mode 100644
index 0000000000..78a9c27606
--- /dev/null
+++ b/activejob/lib/rails/generators/job/job_generator.rb
@@ -0,0 +1,22 @@
+require 'rails/generators/named_base'
+
+module Rails
+ module Generators # :nodoc:
+ class JobGenerator < Rails::Generators::NamedBase # :nodoc:
+ desc 'This generator creates an active job file at app/jobs'
+
+ class_option :queue, type: :string, default: 'default', desc: 'The queue name for the generated job'
+
+ def self.default_generator_root
+ File.dirname(__FILE__)
+ end
+
+ check_class_collision suffix: 'Job'
+
+ def create_job_file
+ template 'job.rb', File.join('app/jobs', class_path, "#{file_name}_job.rb")
+ end
+
+ end
+ end
+end
diff --git a/activejob/lib/rails/generators/job/templates/job.rb b/activejob/lib/rails/generators/job/templates/job.rb
new file mode 100644
index 0000000000..462c71d917
--- /dev/null
+++ b/activejob/lib/rails/generators/job/templates/job.rb
@@ -0,0 +1,9 @@
+<% module_namespacing do -%>
+class <%= class_name %>Job < ActiveJob::Base
+ queue_as :<%= options[:queue] %>
+
+ def perform(*args)
+ # Do something later
+ end
+end
+<% end -%>