diff options
Diffstat (limited to 'activejob/lib/active_job')
17 files changed, 177 insertions, 46 deletions
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb index 622c37098e..8e462bfe5d 100644 --- a/activejob/lib/active_job/arguments.rb +++ b/activejob/lib/active_job/arguments.rb @@ -5,6 +5,8 @@ module ActiveJob # # Wraps the original exception raised as +original_exception+. class DeserializationError < StandardError + # The original exception that was raised during deserialization of job + # arguments. attr_reader :original_exception def initialize(e) #:nodoc: @@ -14,16 +16,17 @@ module ActiveJob end end - # Raised when an unsupported argument type is being set as job argument. We + # Raised when an unsupported argument type is set as a job argument. We # currently support NilClass, Fixnum, Float, String, TrueClass, FalseClass, - # Bignum and object that can be represented as GlobalIDs (ex: Active Record). - # Also raised if you set the key for a Hash something else than a string or - # a symbol. - class SerializationError < ArgumentError - end + # Bignum and objects that can be represented as GlobalIDs (ex: Active Record). + # Raised if you set the key for a Hash something else than a string or + # a symbol. Also raised when trying to serialize an object which can't be + # identified with a Global ID - such as an unpersisted Active Record model. + class SerializationError < ArgumentError; end module Arguments extend self + # :nodoc: TYPE_WHITELIST = [ NilClass, Fixnum, Float, String, TrueClass, FalseClass, Bignum ] # Serializes a set of arguments. Whitelisted types are returned @@ -43,8 +46,11 @@ module ActiveJob end private + # :nodoc: GLOBALID_KEY = '_aj_globalid'.freeze + # :nodoc: SYMBOL_KEYS_KEY = '_aj_symbol_keys'.freeze + # :nodoc: WITH_INDIFFERENT_ACCESS_KEY = '_aj_hash_with_indifferent_access'.freeze private_constant :GLOBALID_KEY, :SYMBOL_KEYS_KEY, :WITH_INDIFFERENT_ACCESS_KEY @@ -53,7 +59,7 @@ module ActiveJob when *TYPE_WHITELIST argument when GlobalID::Identification - { GLOBALID_KEY => argument.to_global_id.to_s } + convert_to_global_id_hash(argument) when Array argument.map { |arg| serialize_argument(arg) } when ActiveSupport::HashWithIndifferentAccess @@ -113,6 +119,7 @@ module ActiveJob result end + # :nodoc: RESERVED_KEYS = [ GLOBALID_KEY, GLOBALID_KEY.to_sym, SYMBOL_KEYS_KEY, SYMBOL_KEYS_KEY.to_sym, @@ -140,5 +147,12 @@ module ActiveJob end end end + + def convert_to_global_id_hash(argument) + { GLOBALID_KEY => argument.to_global_id.to_s } + rescue URI::GID::MissingModelIdError + raise SerializationError, "Unable to serialize #{argument.class} " \ + "without an id. (Maybe you forgot to call save?)" + end end end diff --git a/activejob/lib/active_job/base.rb b/activejob/lib/active_job/base.rb index fd49b3fda5..5d7c4cfb91 100644 --- a/activejob/lib/active_job/base.rb +++ b/activejob/lib/active_job/base.rb @@ -5,6 +5,7 @@ require 'active_job/enqueuing' require 'active_job/execution' require 'active_job/callbacks' require 'active_job/logging' +require 'active_job/translation' module ActiveJob #:nodoc: # = Active Job @@ -60,6 +61,7 @@ module ActiveJob #:nodoc: include Execution include Callbacks include Logging + include Translation ActiveSupport.run_load_hooks(:active_job, self) end diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb index ddd7d1361c..eac7279309 100644 --- a/activejob/lib/active_job/core.rb +++ b/activejob/lib/active_job/core.rb @@ -1,4 +1,6 @@ module ActiveJob + # Provides general behavior that will be included into every Active Job + # object that inherits from ActiveJob::Base. module Core extend ActiveSupport::Concern @@ -15,6 +17,12 @@ module ActiveJob # Queue in which the job will reside. attr_writer :queue_name + + # ID optionally provided by adapter + attr_accessor :provider_job_id + + # I18n.locale to be used during the job. + attr_accessor :locale end # These methods will be included into any Active Job object, adding @@ -63,7 +71,8 @@ module ActiveJob 'job_class' => self.class.name, 'job_id' => job_id, 'queue_name' => queue_name, - 'arguments' => serialize_arguments(arguments) + 'arguments' => serialize_arguments(arguments), + 'locale' => I18n.locale } end @@ -91,6 +100,7 @@ module ActiveJob self.job_id = job_data['job_id'] self.queue_name = job_data['queue_name'] self.serialized_arguments = job_data['arguments'] + self.locale = job_data['locale'] || I18n.locale end private diff --git a/activejob/lib/active_job/enqueuing.rb b/activejob/lib/active_job/enqueuing.rb index 430c17e1bf..98d92385dd 100644 --- a/activejob/lib/active_job/enqueuing.rb +++ b/activejob/lib/active_job/enqueuing.rb @@ -1,6 +1,7 @@ require 'active_job/arguments' module ActiveJob + # Provides behavior for enqueuing and retrying jobs. module Enqueuing extend ActiveSupport::Concern diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb index 9c4519432d..457015b741 100644 --- a/activejob/lib/active_job/queue_adapter.rb +++ b/activejob/lib/active_job/queue_adapter.rb @@ -4,7 +4,7 @@ require 'active_support/core_ext/string/inflections' module ActiveJob # The <tt>ActiveJob::QueueAdapter</tt> module is used to load the - # correct adapter. The default queue adapter is the :inline queue. + # correct adapter. The default queue adapter is the +:inline+ queue. module QueueAdapter #:nodoc: extend ActiveSupport::Concern @@ -15,12 +15,14 @@ module ActiveJob # Includes the setter method for changing the active queue adapter. module ClassMethods + # Returns the backend queue provider. The default queue adapter + # is the +:inline+ queue. See QueueAdapters for more information. def queue_adapter _queue_adapter end # Specify the backend queue provider. The default queue adapter - # is the :inline queue. See QueueAdapters for more + # is the +:inline+ queue. See QueueAdapters for more # information. def queue_adapter=(name_or_adapter_or_class) self._queue_adapter = interpret_adapter(name_or_adapter_or_class) diff --git a/activejob/lib/active_job/queue_adapters.rb b/activejob/lib/active_job/queue_adapters.rb index b3d91dc562..e8ceabaeba 100644 --- a/activejob/lib/active_job/queue_adapters.rb +++ b/activejob/lib/active_job/queue_adapters.rb @@ -15,25 +15,87 @@ module ActiveJob # # === Backends Features # - # | | Async | Queues | Delayed | Priorities | Timeout | Retries | - # |-------------------|-------|--------|-----------|------------|---------|---------| - # | Backburner | Yes | Yes | Yes | Yes | Job | Global | - # | Delayed Job | Yes | Yes | Yes | Job | Global | Global | - # | Qu | Yes | Yes | No | No | No | Global | - # | Que | Yes | Yes | Yes | Job | No | Job | - # | queue_classic | Yes | Yes | No* | No | No | No | - # | Resque | Yes | Yes | Yes (Gem) | Queue | Global | Yes | - # | Sidekiq | Yes | Yes | Yes | Queue | No | Job | - # | Sneakers | Yes | Yes | No | Queue | Queue | No | - # | Sucker Punch | Yes | Yes | No | No | No | No | - # | Active Job Inline | No | Yes | N/A | N/A | N/A | N/A | - # | Active Job | Yes | Yes | Yes | No | No | No | + # | | Async | Queues | Delayed | Priorities | Timeout | Retries | + # |-------------------|-------|--------|------------|------------|---------|---------| + # | Backburner | Yes | Yes | Yes | Yes | Job | Global | + # | Delayed Job | Yes | Yes | Yes | Job | Global | Global | + # | Qu | Yes | Yes | No | No | No | Global | + # | Que | Yes | Yes | Yes | Job | No | Job | + # | queue_classic | Yes | Yes | Yes* | No | No | No | + # | Resque | Yes | Yes | Yes (Gem) | Queue | Global | Yes | + # | Sidekiq | Yes | Yes | Yes | Queue | No | Job | + # | Sneakers | Yes | Yes | No | Queue | Queue | No | + # | Sucker Punch | Yes | Yes | No | No | No | No | + # | Active Job Inline | No | Yes | N/A | N/A | N/A | N/A | + # + # ==== Async + # + # Yes: The Queue Adapter runs the jobs in a separate or forked process. + # + # No: The job is run in the same process. + # + # ==== Queues + # + # Yes: Jobs may set which queue they are run in with queue_as or by using the set + # method. + # + # ==== Delayed + # + # Yes: The adapter will run the job in the future through perform_later. + # + # (Gem): An additional gem is required to use perform_later with this adapter. + # + # No: The adapter will run jobs at the next opportunity and cannot use perform_later. + # + # N/A: The adapter does not support queueing. # # NOTE: - # queue_classic does not support Job scheduling. However you can implement this - # yourself or you can use the queue_classic-later gem. See the documentation for - # ActiveJob::QueueAdapters::QueueClassicAdapter. + # queue_classic supports job scheduling since version 3.1. + # For older versions you can use the queue_classic-later gem. + # + # ==== Priorities + # + # The order in which jobs are processed can be configured differently depending + # on the adapter. + # + # Job: Any class inheriting from the adapter may set the priority on the job + # object relative to other jobs. + # + # Queue: The adapter can set the priority for job queues, when setting a queue + # with Active Job this will be respected. + # + # Yes: Allows the priority to be set on the job object, at the queue level or + # as default configuration option. + # + # No: Does not allow the priority of jobs to be configured. + # + # N/A: The adapter does not support queueing, and therefore sorting them. + # + # ==== Timeout + # + # When a job will stop after the allotted time. + # + # Job: The timeout can be set for each instance of the job class. + # + # Queue: The timeout is set for all jobs on the queue. + # + # Global: The adapter is configured that all jobs have a maximum run time. + # + # N/A: This adapter does not run in a separate process, and therefore timeout + # is unsupported. + # + # ==== Retries + # + # Job: The number of retries can be set per instance of the job class. + # + # Yes: The Number of retries can be configured globally, for each instance or + # on the queue. This adapter may also present failed instances of the job class + # that can be restarted. + # + # Global: The adapter has a global number of retries. # + # N/A: The adapter does not run in a separate process, and therefore doesn't + # support retries. module QueueAdapters extend ActiveSupport::Autoload @@ -53,6 +115,10 @@ module ActiveJob private_constant :ADAPTER class << self + # Returns adapter for specified name. + # + # ActiveJob::QueueAdapters.lookup(:sidekiq) + # # => ActiveJob::QueueAdapters::SidekiqAdapter def lookup(name) const_get(name.to_s.camelize << ADAPTER) 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 index 852a6ee326..ac83da2b9c 100644 --- a/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb @@ -14,11 +14,15 @@ module ActiveJob # Rails.application.config.active_job.queue_adapter = :delayed_job class DelayedJobAdapter def enqueue(job) #:nodoc: - Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name) + delayed_job = Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name) + job.provider_job_id = delayed_job.id + delayed_job end def enqueue_at(job, timestamp) #:nodoc: - Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name, run_at: Time.at(timestamp)) + delayed_job = Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name, run_at: Time.at(timestamp)) + job.provider_job_id = delayed_job.id + delayed_job end class JobWrapper #:nodoc: diff --git a/activejob/lib/active_job/queue_adapters/inline_adapter.rb b/activejob/lib/active_job/queue_adapters/inline_adapter.rb index 1d06324c18..8ad5f4de07 100644 --- a/activejob/lib/active_job/queue_adapters/inline_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/inline_adapter.rb @@ -14,7 +14,7 @@ module ActiveJob end def enqueue_at(*) #:nodoc: - raise NotImplementedError.new("Use a queueing backend to enqueue jobs in the future. Read more at http://guides.rubyonrails.org/active_job_basics.html") + raise NotImplementedError, "Use a queueing backend to enqueue jobs in the future. Read more at http://guides.rubyonrails.org/active_job_basics.html" 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 index 94584ef9d8..0e198922fc 100644 --- a/activejob/lib/active_job/queue_adapters/qu_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/qu_adapter.rb @@ -17,13 +17,17 @@ module ActiveJob # Rails.application.config.active_job.queue_adapter = :qu class QuAdapter def enqueue(job, *args) #:nodoc: - Qu::Payload.new(klass: JobWrapper, args: [job.serialize]).tap do |payload| + qu_job = Qu::Payload.new(klass: JobWrapper, args: [job.serialize]).tap do |payload| payload.instance_variable_set(:@queue, job.queue_name) end.push + + # qu_job can be nil depending on the configured backend + job.provider_job_id = qu_job.id unless qu_job.nil? + qu_job end def enqueue_at(job, timestamp, *args) #:nodoc: - raise NotImplementedError + raise NotImplementedError, "This queueing backend does not support scheduling jobs. To see what features are supported go to http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html" end class JobWrapper < Qu::Job #:nodoc: diff --git a/activejob/lib/active_job/queue_adapters/que_adapter.rb b/activejob/lib/active_job/queue_adapters/que_adapter.rb index 84cc2845b0..90947aa98d 100644 --- a/activejob/lib/active_job/queue_adapters/que_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/que_adapter.rb @@ -16,11 +16,15 @@ module ActiveJob # Rails.application.config.active_job.queue_adapter = :que class QueAdapter def enqueue(job) #:nodoc: - JobWrapper.enqueue job.serialize, queue: job.queue_name + que_job = JobWrapper.enqueue job.serialize + job.provider_job_id = que_job.attrs["job_id"] + que_job end def enqueue_at(job, timestamp) #:nodoc: - JobWrapper.enqueue job.serialize, queue: job.queue_name, run_at: Time.at(timestamp) + que_job = JobWrapper.enqueue job.serialize, run_at: Time.at(timestamp) + job.provider_job_id = que_job.attrs["job_id"] + que_job end class JobWrapper < Que::Job #:nodoc: diff --git a/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb b/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb index 059754a87f..0ee41407d8 100644 --- a/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb @@ -18,7 +18,9 @@ module ActiveJob # Rails.application.config.active_job.queue_adapter = :queue_classic class QueueClassicAdapter def enqueue(job) #:nodoc: - build_queue(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.serialize) + qc_job = build_queue(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.serialize) + job.provider_job_id = qc_job["id"] if qc_job.is_a?(Hash) + qc_job end def enqueue_at(job, timestamp) #:nodoc: @@ -28,7 +30,9 @@ module ActiveJob 'the QC::Queue needs to respond to `enqueue_at(timestamp, method, *args)`. ' \ 'You can implement this yourself or you can use the queue_classic-later gem.' end - queue.enqueue_at(timestamp, "#{JobWrapper.name}.perform", job.serialize) + qc_job = queue.enqueue_at(timestamp, "#{JobWrapper.name}.perform", job.serialize) + job.provider_job_id = qc_job["id"] if qc_job.is_a?(Hash) + qc_job end # Builds a <tt>QC::Queue</tt> object to schedule jobs on. diff --git a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb index 743d5ea333..c321776bf5 100644 --- a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb @@ -17,7 +17,7 @@ module ActiveJob class SidekiqAdapter def enqueue(job) #:nodoc: #Sidekiq::Client does not support symbols as keys - Sidekiq::Client.push \ + job.provider_job_id = Sidekiq::Client.push \ 'class' => JobWrapper, 'wrapped' => job.class.to_s, 'queue' => job.queue_name, @@ -25,7 +25,7 @@ module ActiveJob end def enqueue_at(job, timestamp) #:nodoc: - Sidekiq::Client.push \ + job.provider_job_id = Sidekiq::Client.push \ 'class' => JobWrapper, 'wrapped' => job.class.to_s, 'queue' => job.queue_name, diff --git a/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb b/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb index f5737487ca..f102c6567e 100644 --- a/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb @@ -28,7 +28,7 @@ module ActiveJob end def enqueue_at(job, timestamp) #:nodoc: - raise NotImplementedError + raise NotImplementedError, "This queueing backend does not support scheduling jobs. To see what features are supported go to http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html" end class JobWrapper #:nodoc: diff --git a/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb b/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb index 64c93e8198..c6c35f8ab4 100644 --- a/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb @@ -23,7 +23,7 @@ module ActiveJob end def enqueue_at(job, timestamp) #:nodoc: - raise NotImplementedError + raise NotImplementedError, "This queueing backend does not support scheduling jobs. To see what features are supported go to http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html" end class JobWrapper #:nodoc: diff --git a/activejob/lib/active_job/queue_name.rb b/activejob/lib/active_job/queue_name.rb index 9ae0345120..65786a49ff 100644 --- a/activejob/lib/active_job/queue_name.rb +++ b/activejob/lib/active_job/queue_name.rb @@ -39,7 +39,7 @@ module ActiveJob self.queue_name_delimiter = '_' # set default delimiter to '_' end - # Returns the name of the queue the job will be run on + # Returns the name of the queue the job will be run on. def queue_name if @queue_name.is_a?(Proc) @queue_name = self.class.queue_name_from_part(instance_exec(&@queue_name)) diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index 4efb4b72d2..74a12884ff 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -7,7 +7,7 @@ module ActiveJob extend ActiveSupport::Concern included do - def before_setup + def before_setup # :nodoc: test_adapter = ActiveJob::QueueAdapters::TestAdapter.new @old_queue_adapters = (ActiveJob::Base.subclasses << ActiveJob::Base).select do |klass| @@ -24,7 +24,7 @@ module ActiveJob super end - def after_teardown + def after_teardown # :nodoc: super @old_queue_adapters.each do |(klass, adapter)| klass.queue_adapter = adapter @@ -68,7 +68,7 @@ module ActiveJob original_count = enqueued_jobs_size(only: only) yield new_count = enqueued_jobs_size(only: only) - assert_equal original_count + number, new_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued" + assert_equal number, new_count - original_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued" else actual_count = enqueued_jobs_size(only: only) assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued" @@ -164,7 +164,7 @@ module ActiveJob original_count = performed_jobs.size perform_enqueued_jobs(only: only) { yield } new_count = performed_jobs.size - assert_equal original_count + number, new_count, + assert_equal number, new_count - original_count, "#{number} jobs expected, but #{new_count - original_count} were performed" else performed_jobs_size = performed_jobs.size @@ -233,10 +233,11 @@ module ActiveJob args.assert_valid_keys(:job, :args, :at, :queue) serialized_args = serialize_args_for_assertion(args) yield - matching_job = enqueued_jobs.any? do |job| + matching_job = enqueued_jobs.find do |job| serialized_args.all? { |key, value| value == job[key] } end assert matching_job, "No enqueued job found with #{args}" + instanciate_job(matching_job) ensure queue_adapter.enqueued_jobs = original_enqueued_jobs + enqueued_jobs end @@ -254,10 +255,11 @@ module ActiveJob args.assert_valid_keys(:job, :args, :at, :queue) serialized_args = serialize_args_for_assertion(args) perform_enqueued_jobs { yield } - matching_job = performed_jobs.any? do |job| + matching_job = performed_jobs.find do |job| serialized_args.all? { |key, value| value == job[key] } end assert matching_job, "No performed job found with #{args}" + instanciate_job(matching_job) ensure queue_adapter.performed_jobs = original_performed_jobs + performed_jobs end @@ -311,6 +313,13 @@ module ActiveJob end serialized_args end + + def instanciate_job(payload) + job = payload[:job].new(*payload[:args]) + job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at) + job.queue_name = payload[:queue] + job + end end end end diff --git a/activejob/lib/active_job/translation.rb b/activejob/lib/active_job/translation.rb new file mode 100644 index 0000000000..67e4cf4ab9 --- /dev/null +++ b/activejob/lib/active_job/translation.rb @@ -0,0 +1,11 @@ +module ActiveJob + module Translation #:nodoc: + extend ActiveSupport::Concern + + included do + around_perform do |job, block, _| + I18n.with_locale(job.locale, &block) + end + end + end +end |