diff options
Diffstat (limited to 'activejob/lib')
29 files changed, 384 insertions, 115 deletions
diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb index 56dab66544..01fab4d918 100644 --- a/activejob/lib/active_job.rb +++ b/activejob/lib/active_job.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true #-- -# Copyright (c) 2014-2017 David Heinemeier Hansson +# Copyright (c) 2014-2018 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -25,7 +25,7 @@ require "active_support" require "active_support/rails" -require_relative "active_job/version" +require "active_job/version" require "global_id" module ActiveJob @@ -33,6 +33,7 @@ module ActiveJob autoload :Base autoload :QueueAdapters + autoload :Serializers autoload :ConfiguredJob autoload :TestCase autoload :TestHelper diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb index de11e7fcb1..86bb0c5540 100644 --- a/activejob/lib/active_job/arguments.rb +++ b/activejob/lib/active_job/arguments.rb @@ -14,8 +14,8 @@ module ActiveJob end # Raised when an unsupported argument type is set as a job argument. We - # currently support NilClass, Integer, Fixnum, Float, String, TrueClass, FalseClass, - # Bignum, BigDecimal, and objects that can be represented as GlobalIDs (ex: Active Record). + # currently support NilClass, Integer, Float, String, TrueClass, FalseClass, + # BigDecimal, 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. @@ -25,7 +25,6 @@ module ActiveJob extend self # :nodoc: TYPE_WHITELIST = [ NilClass, String, Integer, Float, BigDecimal, TrueClass, FalseClass ] - TYPE_WHITELIST.push(Fixnum, Bignum) unless 1.class == Integer # Serializes a set of arguments. Whitelisted types are returned # as-is. Arrays/Hashes are serialized element by element. @@ -44,13 +43,24 @@ 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 + # :nodoc: + OBJECT_SERIALIZER_KEY = "_aj_serialized" + + # :nodoc: + RESERVED_KEYS = [ + GLOBALID_KEY, GLOBALID_KEY.to_sym, + SYMBOL_KEYS_KEY, SYMBOL_KEYS_KEY.to_sym, + OBJECT_SERIALIZER_KEY, OBJECT_SERIALIZER_KEY.to_sym, + WITH_INDIFFERENT_ACCESS_KEY, WITH_INDIFFERENT_ACCESS_KEY.to_sym, + ] + private_constant :RESERVED_KEYS def serialize_argument(argument) case argument @@ -70,7 +80,7 @@ module ActiveJob result[SYMBOL_KEYS_KEY] = symbol_keys result else - raise SerializationError.new("Unsupported argument type: #{argument.class.name}") + Serializers.serialize(argument) end end @@ -85,6 +95,8 @@ module ActiveJob when Hash if serialized_global_id?(argument) deserialize_global_id argument + elsif custom_serialized?(argument) + Serializers.deserialize(argument) else deserialize_hash(argument) end @@ -101,6 +113,10 @@ module ActiveJob GlobalID::Locator.locate hash[GLOBALID_KEY] end + def custom_serialized?(hash) + hash.key?(OBJECT_SERIALIZER_KEY) + end + def serialize_hash(argument) argument.each_with_object({}) do |(key, value), hash| hash[serialize_hash_key(key)] = serialize_argument(value) @@ -117,14 +133,6 @@ module ActiveJob result end - # :nodoc: - RESERVED_KEYS = [ - GLOBALID_KEY, GLOBALID_KEY.to_sym, - SYMBOL_KEYS_KEY, SYMBOL_KEYS_KEY.to_sym, - WITH_INDIFFERENT_ACCESS_KEY, WITH_INDIFFERENT_ACCESS_KEY.to_sym, - ] - private_constant :RESERVED_KEYS - def serialize_hash_key(key) case key when *RESERVED_KEYS diff --git a/activejob/lib/active_job/base.rb b/activejob/lib/active_job/base.rb index 6af41260db..2b2a59e969 100644 --- a/activejob/lib/active_job/base.rb +++ b/activejob/lib/active_job/base.rb @@ -1,15 +1,16 @@ # frozen_string_literal: true -require_relative "core" -require_relative "queue_adapter" -require_relative "queue_name" -require_relative "queue_priority" -require_relative "enqueuing" -require_relative "execution" -require_relative "callbacks" -require_relative "exceptions" -require_relative "logging" -require_relative "translation" +require "active_job/core" +require "active_job/queue_adapter" +require "active_job/queue_name" +require "active_job/queue_priority" +require "active_job/enqueuing" +require "active_job/execution" +require "active_job/callbacks" +require "active_job/exceptions" +require "active_job/logging" +require "active_job/timezones" +require "active_job/translation" module ActiveJob #:nodoc: # = Active Job @@ -59,6 +60,7 @@ module ActiveJob #:nodoc: # * SerializationError - Error class for serialization errors. class Base include Core + include Serializers include QueueAdapter include QueueName include QueuePriority @@ -67,6 +69,7 @@ module ActiveJob #:nodoc: include Callbacks include Exceptions include Logging + include Timezones include Translation ActiveSupport.run_load_hooks(:active_job, self) diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb index c4e12fc518..61d402cfca 100644 --- a/activejob/lib/active_job/core.rb +++ b/activejob/lib/active_job/core.rb @@ -31,6 +31,9 @@ module ActiveJob # I18n.locale to be used during the job. attr_accessor :locale + + # Timezone to be used during the job. + attr_accessor :timezone end # These methods will be included into any Active Job object, adding @@ -85,9 +88,10 @@ module ActiveJob "provider_job_id" => provider_job_id, "queue_name" => queue_name, "priority" => priority, - "arguments" => serialize_arguments(arguments), + "arguments" => serialize_arguments_if_needed(arguments), "executions" => executions, - "locale" => I18n.locale.to_s + "locale" => I18n.locale.to_s, + "timezone" => Time.zone.try(:name) } end @@ -97,17 +101,23 @@ module ActiveJob # ==== Examples # # class DeliverWebhookJob < ActiveJob::Base + # attr_writer :attempt_number + # + # def attempt_number + # @attempt_number ||= 0 + # end + # # def serialize - # super.merge('attempt_number' => (@attempt_number || 0) + 1) + # super.merge('attempt_number' => attempt_number + 1) # end # # def deserialize(job_data) # super - # @attempt_number = job_data['attempt_number'] + # self.attempt_number = job_data['attempt_number'] # end # - # rescue_from(TimeoutError) do |exception| - # raise exception if @attempt_number > 5 + # rescue_from(Timeout::Error) do |exception| + # raise exception if attempt_number > 5 # retry_job(wait: 10) # end # end @@ -119,22 +129,35 @@ module ActiveJob self.serialized_arguments = job_data["arguments"] self.executions = job_data["executions"] self.locale = job_data["locale"] || I18n.locale.to_s + self.timezone = job_data["timezone"] || Time.zone.try(:name) end private + def serialize_arguments_if_needed(arguments) + if arguments_serialized? + @serialized_arguments + else + serialize_arguments(arguments) + end + end + def deserialize_arguments_if_needed - if defined?(@serialized_arguments) && @serialized_arguments.present? + if arguments_serialized? @arguments = deserialize_arguments(@serialized_arguments) @serialized_arguments = nil end end - def serialize_arguments(serialized_args) - Arguments.serialize(serialized_args) + def serialize_arguments(arguments) + Arguments.serialize(arguments) end def deserialize_arguments(serialized_args) Arguments.deserialize(serialized_args) end + + def arguments_serialized? + defined?(@serialized_arguments) && @serialized_arguments + end end end diff --git a/activejob/lib/active_job/enqueuing.rb b/activejob/lib/active_job/enqueuing.rb index ad32d3065b..53cb98fc71 100644 --- a/activejob/lib/active_job/enqueuing.rb +++ b/activejob/lib/active_job/enqueuing.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "arguments" +require "active_job/arguments" module ActiveJob # Provides behavior for enqueuing jobs. @@ -10,7 +10,7 @@ module ActiveJob # Includes the +perform_later+ method for job initialization. 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 + # (+string+, +int+, +float+, +nil+, +true+, +false+, +hash+ or +array+) or # GlobalID::Identification instances. Arbitrary Ruby objects # are not supported. # diff --git a/activejob/lib/active_job/exceptions.rb b/activejob/lib/active_job/exceptions.rb index dfc74deb1a..ae700848d0 100644 --- a/activejob/lib/active_job/exceptions.rb +++ b/activejob/lib/active_job/exceptions.rb @@ -49,7 +49,7 @@ module ActiveJob retry_job wait: determine_delay(wait), queue: queue, priority: priority else if block_given? - yield self, exception + yield self, error else logger.error "Stopped retrying #{self.class} due to a #{exception}, which reoccurred on #{executions} attempts. The original exception was #{error.cause.inspect}." raise error @@ -61,18 +61,28 @@ module ActiveJob # Discard the job with no attempts to retry, if the exception is raised. This is useful when the subject of the job, # like an Active Record, is no longer available, and the job is thus no longer relevant. # + # You can also pass a block that'll be invoked. This block is yielded with the job instance as the first and the error instance as the second parameter. + # # ==== Example # # class SearchIndexingJob < ActiveJob::Base # discard_on ActiveJob::DeserializationError + # discard_on(CustomAppException) do |job, exception| + # ExceptionNotifier.caught(exception) + # end # # def perform(record) # # Will raise ActiveJob::DeserializationError if the record can't be deserialized + # # Might raise CustomAppException for something domain specific # end # end def discard_on(exception) rescue_from exception do |error| - logger.error "Discarded #{self.class} due to a #{exception}. The original exception was #{error.cause.inspect}." + if block_given? + yield self, exception + else + logger.error "Discarded #{self.class} due to a #{exception}. The original exception was #{error.cause.inspect}." + end end end end diff --git a/activejob/lib/active_job/execution.rb b/activejob/lib/active_job/execution.rb index 85e050b489..d75be376ec 100644 --- a/activejob/lib/active_job/execution.rb +++ b/activejob/lib/active_job/execution.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "active_support/rescuable" -require_relative "arguments" +require "active_job/arguments" module ActiveJob module Execution diff --git a/activejob/lib/active_job/gem_version.rb b/activejob/lib/active_job/gem_version.rb index 7ee61780e1..770f70dc5e 100644 --- a/activejob/lib/active_job/gem_version.rb +++ b/activejob/lib/active_job/gem_version.rb @@ -7,8 +7,8 @@ module ActiveJob end module VERSION - MAJOR = 5 - MINOR = 2 + MAJOR = 6 + MINOR = 0 TINY = 0 PRE = "alpha" diff --git a/activejob/lib/active_job/logging.rb b/activejob/lib/active_job/logging.rb index f53b7eaee5..3312857ac7 100644 --- a/activejob/lib/active_job/logging.rb +++ b/activejob/lib/active_job/logging.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "active_support/core_ext/hash/transform_values" require "active_support/core_ext/string/filters" require "active_support/tagged_logging" require "active_support/logger" @@ -12,13 +11,13 @@ module ActiveJob included do cattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT)) - around_enqueue do |_, block, _| + around_enqueue do |_, block| tag_logger do block.call end end - around_perform do |job, block, _| + around_perform do |job, block| tag_logger(job.class.name, job.job_id) do payload = { adapter: job.class.queue_adapter, job: job } ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup) diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb index dd05800baf..006a683b85 100644 --- a/activejob/lib/active_job/queue_adapter.rb +++ b/activejob/lib/active_job/queue_adapter.rb @@ -29,28 +29,22 @@ module ActiveJob # Specify the backend queue provider. The default queue adapter # is the +:async+ queue. See QueueAdapters for more # information. - def queue_adapter=(name_or_adapter_or_class) - interpret_adapter(name_or_adapter_or_class) - end - - private - - def interpret_adapter(name_or_adapter_or_class) - case name_or_adapter_or_class - when Symbol, String - assign_adapter(name_or_adapter_or_class.to_s, - ActiveJob::QueueAdapters.lookup(name_or_adapter_or_class).new) + def queue_adapter=(name_or_adapter) + case name_or_adapter + when Symbol, String + queue_adapter = ActiveJob::QueueAdapters.lookup(name_or_adapter).new + assign_adapter(name_or_adapter.to_s, queue_adapter) + else + if queue_adapter?(name_or_adapter) + adapter_name = "#{name_or_adapter.class.name.demodulize.remove('Adapter').underscore}" + assign_adapter(adapter_name, name_or_adapter) else - if queue_adapter?(name_or_adapter_or_class) - adapter_name = "#{name_or_adapter_or_class.class.name.demodulize.remove('Adapter').underscore}" - assign_adapter(adapter_name, - name_or_adapter_or_class) - else - raise ArgumentError - end + raise ArgumentError end end + end + private def assign_adapter(adapter_name, queue_adapter) self._queue_adapter_name = adapter_name self._queue_adapter = queue_adapter diff --git a/activejob/lib/active_job/queue_adapters.rb b/activejob/lib/active_job/queue_adapters.rb index c1a1d3c510..7854467cc1 100644 --- a/activejob/lib/active_job/queue_adapters.rb +++ b/activejob/lib/active_job/queue_adapters.rb @@ -7,7 +7,6 @@ module ActiveJob # # * {Backburner}[https://github.com/nesquena/backburner] # * {Delayed Job}[https://github.com/collectiveidea/delayed_job] - # * {Qu}[https://github.com/bkeepers/qu] # * {Que}[https://github.com/chanks/que] # * {queue_classic}[https://github.com/QueueClassic/queue_classic] # * {Resque}[https://github.com/resque/resque] @@ -16,6 +15,7 @@ module ActiveJob # * {Sucker Punch}[https://github.com/brandonhilkert/sucker_punch] # * {Active Job Async Job}[http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html] # * {Active Job Inline}[http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/InlineAdapter.html] + # * Please Note: We are not accepting pull requests for new adapters. See the README for more details. # # === Backends Features # @@ -23,7 +23,6 @@ module ActiveJob # |-------------------|-------|--------|------------|------------|---------|---------| # | 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 | @@ -114,7 +113,6 @@ module ActiveJob autoload :InlineAdapter autoload :BackburnerAdapter autoload :DelayedJobAdapter - autoload :QuAdapter autoload :QueAdapter autoload :QueueClassicAdapter autoload :ResqueAdapter diff --git a/activejob/lib/active_job/queue_adapters/async_adapter.rb b/activejob/lib/active_job/queue_adapters/async_adapter.rb index eec9ae08bb..ebf6f384e3 100644 --- a/activejob/lib/active_job/queue_adapters/async_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/async_adapter.rb @@ -31,7 +31,7 @@ module ActiveJob # jobs. Since jobs share a single thread pool, long-running jobs will block # short-lived jobs. Fine for dev/test; bad for production. class AsyncAdapter - # See {Concurrent::ThreadPoolExecutor}[http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadPoolExecutor.html] for executor options. + # See {Concurrent::ThreadPoolExecutor}[https://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadPoolExecutor.html] for executor options. def initialize(**executor_options) @scheduler = Scheduler.new(**executor_options) 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 1978179948..8eeef32b99 100644 --- a/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb @@ -34,6 +34,10 @@ module ActiveJob @job_data = job_data end + def display_name + "#{job_data['job_class']} [#{job_data['job_id']}] from DelayedJob(#{job_data['queue_name']}) with arguments: #{job_data['arguments']}" + end + def perform Base.execute(job_data) end diff --git a/activejob/lib/active_job/queue_adapters/qu_adapter.rb b/activejob/lib/active_job/queue_adapters/qu_adapter.rb deleted file mode 100644 index bd7003e177..0000000000 --- a/activejob/lib/active_job/queue_adapters/qu_adapter.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require "qu" - -module ActiveJob - module QueueAdapters - # == Qu adapter for Active Job - # - # Qu is a Ruby library for queuing and processing background jobs. It is - # heavily inspired by delayed_job and Resque. Qu was created to overcome - # some shortcomings in the existing queuing libraries. - # The advantages of Qu are: Multiple backends (redis, mongo), jobs are - # requeued when worker is killed, resque-like API. - # - # Read more about Qu {here}[https://github.com/bkeepers/qu]. - # - # To use Qu set the queue_adapter config to +:qu+. - # - # Rails.application.config.active_job.queue_adapter = :qu - class QuAdapter - def enqueue(job, *args) #:nodoc: - 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, "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: - def initialize(job_data) - @job_data = job_data - end - - def perform - Base.execute @job_data - 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 index 5a1135854b..f726e6ad93 100644 --- a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb @@ -18,7 +18,7 @@ module ActiveJob # Rails.application.config.active_job.queue_adapter = :sidekiq class SidekiqAdapter def enqueue(job) #:nodoc: - #Sidekiq::Client does not support symbols as keys + # Sidekiq::Client does not support symbols as keys job.provider_job_id = Sidekiq::Client.push \ "class" => JobWrapper, "wrapped" => job.class.to_s, diff --git a/activejob/lib/active_job/railtie.rb b/activejob/lib/active_job/railtie.rb index 7b0742a6d2..d0294854d3 100644 --- a/activejob/lib/active_job/railtie.rb +++ b/activejob/lib/active_job/railtie.rb @@ -7,17 +7,28 @@ module ActiveJob # = Active Job Railtie class Railtie < Rails::Railtie # :nodoc: config.active_job = ActiveSupport::OrderedOptions.new + config.active_job.custom_serializers = [] initializer "active_job.logger" do ActiveSupport.on_load(:active_job) { self.logger = ::Rails.logger } end + initializer "active_job.custom_serializers" do |app| + config.after_initialize do + custom_serializers = app.config.active_job.delete(:custom_serializers) + ActiveJob::Serializers.add_serializers custom_serializers + end + end + initializer "active_job.set_configs" do |app| options = app.config.active_job options.queue_adapter ||= :async ActiveSupport.on_load(:active_job) do - options.each { |k, v| send("#{k}=", v) } + options.each do |k, v| + k = "#{k}=" + send(k, v) if respond_to? k + end end end diff --git a/activejob/lib/active_job/serializers.rb b/activejob/lib/active_job/serializers.rb new file mode 100644 index 0000000000..df66e66659 --- /dev/null +++ b/activejob/lib/active_job/serializers.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require "set" + +module ActiveJob + # The <tt>ActiveJob::Serializers</tt> module is used to store a list of known serializers + # and to add new ones. It also has helpers to serialize/deserialize objects. + module Serializers # :nodoc: + extend ActiveSupport::Autoload + extend ActiveSupport::Concern + + autoload :ObjectSerializer + autoload :SymbolSerializer + autoload :DurationSerializer + autoload :DateTimeSerializer + autoload :DateSerializer + autoload :TimeWithZoneSerializer + autoload :TimeSerializer + + mattr_accessor :_additional_serializers + self._additional_serializers = Set.new + + class << self + # Returns serialized representative of the passed object. + # Will look up through all known serializers. + # Raises <tt>ActiveJob::SerializationError</tt> if it can't find a proper serializer. + def serialize(argument) + serializer = serializers.detect { |s| s.serialize?(argument) } + raise SerializationError.new("Unsupported argument type: #{argument.class.name}") unless serializer + serializer.serialize(argument) + end + + # Returns deserialized object. + # Will look up through all known serializers. + # If no serializer found will raise <tt>ArgumentError</tt>. + def deserialize(argument) + serializer_name = argument[Arguments::OBJECT_SERIALIZER_KEY] + raise ArgumentError, "Serializer name is not present in the argument: #{argument.inspect}" unless serializer_name + + serializer = serializer_name.safe_constantize + raise ArgumentError, "Serializer #{serializer_name} is not known" unless serializer + + serializer.deserialize(argument) + end + + # Returns list of known serializers. + def serializers + self._additional_serializers + end + + # Adds new serializers to a list of known serializers. + def add_serializers(*new_serializers) + self._additional_serializers += new_serializers.flatten + end + end + + add_serializers SymbolSerializer, + DurationSerializer, + DateTimeSerializer, + DateSerializer, + TimeWithZoneSerializer, + TimeSerializer + end +end diff --git a/activejob/lib/active_job/serializers/date_serializer.rb b/activejob/lib/active_job/serializers/date_serializer.rb new file mode 100644 index 0000000000..e995d30faa --- /dev/null +++ b/activejob/lib/active_job/serializers/date_serializer.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module ActiveJob + module Serializers + class DateSerializer < ObjectSerializer # :nodoc: + def serialize(date) + super("value" => date.iso8601) + end + + def deserialize(hash) + Date.iso8601(hash["value"]) + end + + private + + def klass + Date + end + end + end +end diff --git a/activejob/lib/active_job/serializers/date_time_serializer.rb b/activejob/lib/active_job/serializers/date_time_serializer.rb new file mode 100644 index 0000000000..fe780a1978 --- /dev/null +++ b/activejob/lib/active_job/serializers/date_time_serializer.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module ActiveJob + module Serializers + class DateTimeSerializer < ObjectSerializer # :nodoc: + def serialize(time) + super("value" => time.iso8601) + end + + def deserialize(hash) + DateTime.iso8601(hash["value"]) + end + + private + + def klass + DateTime + end + end + end +end diff --git a/activejob/lib/active_job/serializers/duration_serializer.rb b/activejob/lib/active_job/serializers/duration_serializer.rb new file mode 100644 index 0000000000..715fe27a5c --- /dev/null +++ b/activejob/lib/active_job/serializers/duration_serializer.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module ActiveJob + module Serializers + class DurationSerializer < ObjectSerializer # :nodoc: + def serialize(duration) + super("value" => duration.value, "parts" => Arguments.serialize(duration.parts)) + end + + def deserialize(hash) + value = hash["value"] + parts = Arguments.deserialize(hash["parts"]) + + klass.new(value, parts) + end + + private + + def klass + ActiveSupport::Duration + end + end + end +end diff --git a/activejob/lib/active_job/serializers/object_serializer.rb b/activejob/lib/active_job/serializers/object_serializer.rb new file mode 100644 index 0000000000..6d280969be --- /dev/null +++ b/activejob/lib/active_job/serializers/object_serializer.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module ActiveJob + module Serializers + # Base class for serializing and deserializing custom objects. + # + # Example: + # + # class MoneySerializer < ActiveJob::Serializers::ObjectSerializer + # def serialize(money) + # super("amount" => money.amount, "currency" => money.currency) + # end + # + # def deserialize(hash) + # Money.new(hash["amount"], hash["currency"]) + # end + # + # private + # + # def klass + # Money + # end + # end + class ObjectSerializer + include Singleton + + class << self + delegate :serialize?, :serialize, :deserialize, to: :instance + end + + # Determines if an argument should be serialized by a serializer. + def serialize?(argument) + argument.is_a?(klass) + end + + # Serializes an argument to a JSON primitive type. + def serialize(hash) + { Arguments::OBJECT_SERIALIZER_KEY => self.class.name }.merge!(hash) + end + + # Deserializes an argument from a JSON primitive type. + def deserialize(_argument) + raise NotImplementedError + end + + private + + # The class of the object that will be serialized. + def klass # :doc: + raise NotImplementedError + end + end + end +end diff --git a/activejob/lib/active_job/serializers/symbol_serializer.rb b/activejob/lib/active_job/serializers/symbol_serializer.rb new file mode 100644 index 0000000000..7e1f9553a2 --- /dev/null +++ b/activejob/lib/active_job/serializers/symbol_serializer.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module ActiveJob + module Serializers + class SymbolSerializer < ObjectSerializer # :nodoc: + def serialize(argument) + super("value" => argument.to_s) + end + + def deserialize(argument) + argument["value"].to_sym + end + + private + + def klass + Symbol + end + end + end +end diff --git a/activejob/lib/active_job/serializers/time_serializer.rb b/activejob/lib/active_job/serializers/time_serializer.rb new file mode 100644 index 0000000000..fe20772f35 --- /dev/null +++ b/activejob/lib/active_job/serializers/time_serializer.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module ActiveJob + module Serializers + class TimeSerializer < ObjectSerializer # :nodoc: + def serialize(time) + super("value" => time.iso8601) + end + + def deserialize(hash) + Time.iso8601(hash["value"]) + end + + private + + def klass + Time + end + end + end +end diff --git a/activejob/lib/active_job/serializers/time_with_zone_serializer.rb b/activejob/lib/active_job/serializers/time_with_zone_serializer.rb new file mode 100644 index 0000000000..43017fc75b --- /dev/null +++ b/activejob/lib/active_job/serializers/time_with_zone_serializer.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module ActiveJob + module Serializers + class TimeWithZoneSerializer < ObjectSerializer # :nodoc: + def serialize(time) + super("value" => time.iso8601) + end + + def deserialize(hash) + Time.iso8601(hash["value"]).in_time_zone + end + + private + + def klass + ActiveSupport::TimeWithZone + end + end + end +end diff --git a/activejob/lib/active_job/timezones.rb b/activejob/lib/active_job/timezones.rb new file mode 100644 index 0000000000..ac018eb752 --- /dev/null +++ b/activejob/lib/active_job/timezones.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module ActiveJob + module Timezones #:nodoc: + extend ActiveSupport::Concern + + included do + around_perform do |job, block| + Time.use_zone(job.timezone, &block) + end + end + end +end diff --git a/activejob/lib/active_job/translation.rb b/activejob/lib/active_job/translation.rb index fb45c80d67..0fd9b9fc06 100644 --- a/activejob/lib/active_job/translation.rb +++ b/activejob/lib/active_job/translation.rb @@ -5,7 +5,7 @@ module ActiveJob extend ActiveSupport::Concern included do - around_perform do |job, block, _| + around_perform do |job, block| I18n.with_locale(job.locale, &block) end end diff --git a/activejob/lib/rails/generators/job/job_generator.rb b/activejob/lib/rails/generators/job/job_generator.rb index 69b4fe7d26..03346a7f12 100644 --- a/activejob/lib/rails/generators/job/job_generator.rb +++ b/activejob/lib/rails/generators/job/job_generator.rb @@ -28,6 +28,10 @@ module Rails # :nodoc: end private + def file_name + @_file_name ||= super.sub(/_job\z/i, "") + end + def application_job_file_name @application_job_file_name ||= if mountable_engine? "app/jobs/#{namespaced_path}/application_job.rb" diff --git a/activejob/lib/rails/generators/job/templates/application_job.rb b/activejob/lib/rails/generators/job/templates/application_job.rb.tt index f93745a31a..f93745a31a 100644 --- a/activejob/lib/rails/generators/job/templates/application_job.rb +++ b/activejob/lib/rails/generators/job/templates/application_job.rb.tt diff --git a/activejob/lib/rails/generators/job/templates/job.rb b/activejob/lib/rails/generators/job/templates/job.rb.tt index 4ad2914a45..4ad2914a45 100644 --- a/activejob/lib/rails/generators/job/templates/job.rb +++ b/activejob/lib/rails/generators/job/templates/job.rb.tt |