aboutsummaryrefslogtreecommitdiffstats
path: root/activejob
diff options
context:
space:
mode:
authorGuillermo Iguaran <guilleiguaran@gmail.com>2018-02-24 18:03:47 -0500
committerGitHub <noreply@github.com>2018-02-24 18:03:47 -0500
commit697dd48b5e5787126a91ce10739f8af31d1ffd1d (patch)
treecc4ff296f2096afe1a5fd3057f101213caac149a /activejob
parent6f5cca77313e127313ea44c5c213fda3b9027a95 (diff)
parent3915a470d2b8898fdbc384d0f9f31e2ad8a2c899 (diff)
downloadrails-697dd48b5e5787126a91ce10739f8af31d1ffd1d.tar.gz
rails-697dd48b5e5787126a91ce10739f8af31d1ffd1d.tar.bz2
rails-697dd48b5e5787126a91ce10739f8af31d1ffd1d.zip
Merge branch 'master' into update_default_hsts_max_age
Diffstat (limited to 'activejob')
-rw-r--r--activejob/CHANGELOG.md18
-rw-r--r--activejob/activejob.gemspec2
-rw-r--r--activejob/lib/active_job.rb1
-rw-r--r--activejob/lib/active_job/arguments.rb34
-rw-r--r--activejob/lib/active_job/base.rb3
-rw-r--r--activejob/lib/active_job/core.rb7
-rw-r--r--activejob/lib/active_job/exceptions.rb12
-rw-r--r--activejob/lib/active_job/gem_version.rb6
-rw-r--r--activejob/lib/active_job/logging.rb5
-rw-r--r--activejob/lib/active_job/railtie.rb13
-rw-r--r--activejob/lib/active_job/serializers.rb64
-rw-r--r--activejob/lib/active_job/serializers/date_serializer.rb21
-rw-r--r--activejob/lib/active_job/serializers/date_time_serializer.rb21
-rw-r--r--activejob/lib/active_job/serializers/duration_serializer.rb24
-rw-r--r--activejob/lib/active_job/serializers/object_serializer.rb54
-rw-r--r--activejob/lib/active_job/serializers/symbol_serializer.rb21
-rw-r--r--activejob/lib/active_job/serializers/time_serializer.rb21
-rw-r--r--activejob/lib/active_job/serializers/time_with_zone_serializer.rb21
-rw-r--r--activejob/lib/active_job/timezones.rb13
-rw-r--r--activejob/lib/active_job/translation.rb2
-rw-r--r--activejob/test/cases/argument_serialization_test.rb56
-rw-r--r--activejob/test/cases/exceptions_test.rb7
-rw-r--r--activejob/test/cases/job_serialization_test.rb7
-rw-r--r--activejob/test/cases/serializers_test.rb98
-rw-r--r--activejob/test/cases/timezones_test.rb24
-rw-r--r--activejob/test/integration/queuing_test.rb16
-rw-r--r--activejob/test/jobs/retry_job.rb2
-rw-r--r--activejob/test/jobs/timezone_dependent_job.rb22
-rw-r--r--activejob/test/support/integration/dummy_app_template.rb1
-rw-r--r--activejob/test/support/integration/test_case_helpers.rb4
30 files changed, 570 insertions, 30 deletions
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index 4453f845f4..c6a3ad8ade 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1,12 +1,20 @@
-## Rails 5.2.0.beta2 (November 28, 2017) ##
+## Rails 6.0.0.alpha (Unreleased) ##
-* No changes.
+* Add support for timezones to Active Job
+ Record what was the current timezone in effect when the job was
+ enqueued and then restore when the job is executed in same way
+ that the current locale is recorded and restored.
-## Rails 5.2.0.beta1 (November 27, 2017) ##
+ *Andrew White*
-* Support redis-rb 4.0.
+* Rails 6 requires Ruby 2.4.1 or newer.
*Jeremy Daer*
-Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activejob/CHANGELOG.md) for previous changes.
+* Add support to define custom argument serializers.
+
+ *Evgenii Pecherkin*, *Rafael Mendonça França*
+
+
+Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/activejob/CHANGELOG.md) for previous changes.
diff --git a/activejob/activejob.gemspec b/activejob/activejob.gemspec
index 71e32f695b..be6292f737 100644
--- a/activejob/activejob.gemspec
+++ b/activejob/activejob.gemspec
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
s.summary = "Job framework with pluggable queues."
s.description = "Declare job classes that can be run by a variety of queueing backends."
- s.required_ruby_version = ">= 2.2.2"
+ s.required_ruby_version = ">= 2.4.1"
s.license = "MIT"
diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb
index 626abaa767..01fab4d918 100644
--- a/activejob/lib/active_job.rb
+++ b/activejob/lib/active_job.rb
@@ -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 ae112abb2c..2b2a59e969 100644
--- a/activejob/lib/active_job/base.rb
+++ b/activejob/lib/active_job/base.rb
@@ -9,6 +9,7 @@ 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:
@@ -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 879746fc01..da841ae45b 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
@@ -87,7 +90,8 @@ module ActiveJob
"priority" => priority,
"arguments" => serialize_arguments(arguments),
"executions" => executions,
- "locale" => I18n.locale.to_s
+ "locale" => I18n.locale.to_s,
+ "timezone" => Time.zone.try(:name)
}
end
@@ -125,6 +129,7 @@ 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
diff --git a/activejob/lib/active_job/exceptions.rb b/activejob/lib/active_job/exceptions.rb
index 8b4a88ba6a..ae700848d0 100644
--- a/activejob/lib/active_job/exceptions.rb
+++ b/activejob/lib/active_job/exceptions.rb
@@ -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/gem_version.rb b/activejob/lib/active_job/gem_version.rb
index 49dfd4095e..770f70dc5e 100644
--- a/activejob/lib/active_job/gem_version.rb
+++ b/activejob/lib/active_job/gem_version.rb
@@ -7,10 +7,10 @@ module ActiveJob
end
module VERSION
- MAJOR = 5
- MINOR = 2
+ MAJOR = 6
+ MINOR = 0
TINY = 0
- PRE = "beta2"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
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/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..1dfd1e44be
--- /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
+
+ # Deserilizes an argument form a JSON primiteve 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/test/cases/argument_serialization_test.rb b/activejob/test/cases/argument_serialization_test.rb
index 7e7f854da0..e5f1f087fe 100644
--- a/activejob/test/cases/argument_serialization_test.rb
+++ b/activejob/test/cases/argument_serialization_test.rb
@@ -13,6 +13,9 @@ class ArgumentSerializationTest < ActiveSupport::TestCase
[ nil, 1, 1.0, 1_000_000_000_000_000_000_000,
"a", true, false, BigDecimal(5),
+ :a, 1.day, Date.new(2001, 2, 3), Time.new(2002, 10, 31, 2, 2, 2, "+02:00"),
+ DateTime.new(2001, 2, 3, 4, 5, 6, "+03:00"),
+ ActiveSupport::TimeWithZone.new(Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone["UTC"]),
[ 1, "a" ],
{ "a" => 1 }
].each do |arg|
@@ -21,7 +24,7 @@ class ArgumentSerializationTest < ActiveSupport::TestCase
end
end
- [ :a, Object.new, self, Person.find("5").to_gid ].each do |arg|
+ [ Object.new, self, Person.find("5").to_gid ].each do |arg|
test "does not serialize #{arg.class}" do
assert_raises ActiveJob::SerializationError do
ActiveJob::Arguments.serialize [ arg ]
@@ -46,6 +49,49 @@ class ArgumentSerializationTest < ActiveSupport::TestCase
assert_arguments_roundtrip([a: 1, "b" => 2])
end
+ test "serialize a hash" do
+ symbol_key = { a: 1 }
+ string_key = { "a" => 1 }
+ indifferent_access = { a: 1 }.with_indifferent_access
+
+ assert_equal(
+ { "a" => 1, "_aj_symbol_keys" => ["a"] },
+ ActiveJob::Arguments.serialize([symbol_key]).first
+ )
+ assert_equal(
+ { "a" => 1, "_aj_symbol_keys" => [] },
+ ActiveJob::Arguments.serialize([string_key]).first
+ )
+ assert_equal(
+ { "a" => 1, "_aj_hash_with_indifferent_access" => true },
+ ActiveJob::Arguments.serialize([indifferent_access]).first
+ )
+ end
+
+ test "deserialize a hash" do
+ symbol_key = { "a" => 1, "_aj_symbol_keys" => ["a"] }
+ string_key = { "a" => 1, "_aj_symbol_keys" => [] }
+ another_string_key = { "a" => 1 }
+ indifferent_access = { "a" => 1, "_aj_hash_with_indifferent_access" => true }
+
+ assert_equal(
+ { a: 1 },
+ ActiveJob::Arguments.deserialize([symbol_key]).first
+ )
+ assert_equal(
+ { "a" => 1 },
+ ActiveJob::Arguments.deserialize([string_key]).first
+ )
+ assert_equal(
+ { "a" => 1 },
+ ActiveJob::Arguments.deserialize([another_string_key]).first
+ )
+ assert_equal(
+ { "a" => 1 },
+ ActiveJob::Arguments.deserialize([indifferent_access]).first
+ )
+ end
+
test "should maintain hash with indifferent access" do
symbol_key = { a: 1 }
string_key = { "a" => 1 }
@@ -56,6 +102,14 @@ class ArgumentSerializationTest < ActiveSupport::TestCase
assert_instance_of ActiveSupport::HashWithIndifferentAccess, perform_round_trip([indifferent_access]).first
end
+ test "should maintain time with zone" do
+ Time.use_zone "Alaska" do
+ time_with_zone = Time.new(2002, 10, 31, 2, 2, 2).in_time_zone
+ assert_instance_of ActiveSupport::TimeWithZone, perform_round_trip([time_with_zone]).first
+ assert_arguments_unchanged time_with_zone
+ end
+ end
+
test "should disallow non-string/symbol hash keys" do
assert_raises ActiveJob::SerializationError do
ActiveJob::Arguments.serialize [ { 1 => 2 } ]
diff --git a/activejob/test/cases/exceptions_test.rb b/activejob/test/cases/exceptions_test.rb
index 22fed0a808..bc33d79f61 100644
--- a/activejob/test/cases/exceptions_test.rb
+++ b/activejob/test/cases/exceptions_test.rb
@@ -58,6 +58,13 @@ class ExceptionsTest < ActiveJob::TestCase
end
end
+ test "custom handling of discarded job" do
+ perform_enqueued_jobs do
+ RetryJob.perform_later "CustomDiscardableError", 2
+ assert_equal "Dealt with a job that was discarded in a custom way", JobBuffer.last_value
+ end
+ end
+
test "custom handling of job that exceeds retry attempts" do
perform_enqueued_jobs do
RetryJob.perform_later "CustomCatchError", 6
diff --git a/activejob/test/cases/job_serialization_test.rb b/activejob/test/cases/job_serialization_test.rb
index 440051c427..5c9994508e 100644
--- a/activejob/test/cases/job_serialization_test.rb
+++ b/activejob/test/cases/job_serialization_test.rb
@@ -54,4 +54,11 @@ class JobSerializationTest < ActiveSupport::TestCase
job.provider_job_id = "some value set by adapter"
assert_equal job.provider_job_id, job.serialize["provider_job_id"]
end
+
+ test "serialize stores the current timezone" do
+ Time.use_zone "Hawaii" do
+ job = HelloJob.new
+ assert_equal "Hawaii", job.serialize["timezone"]
+ end
+ end
end
diff --git a/activejob/test/cases/serializers_test.rb b/activejob/test/cases/serializers_test.rb
new file mode 100644
index 0000000000..bee0c061bd
--- /dev/null
+++ b/activejob/test/cases/serializers_test.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+require "helper"
+require "active_job/serializers"
+
+class SerializersTest < ActiveSupport::TestCase
+ class DummyValueObject
+ attr_accessor :value
+
+ def initialize(value)
+ @value = value
+ end
+
+ def ==(other)
+ self.value == other.value
+ end
+ end
+
+ class DummySerializer < ActiveJob::Serializers::ObjectSerializer
+ def serialize(object)
+ super({ "value" => object.value })
+ end
+
+ def deserialize(hash)
+ DummyValueObject.new(hash["value"])
+ end
+
+ private
+
+ def klass
+ DummyValueObject
+ end
+ end
+
+ setup do
+ @value_object = DummyValueObject.new 123
+ @original_serializers = ActiveJob::Serializers.serializers
+ end
+
+ teardown do
+ ActiveJob::Serializers._additional_serializers = @original_serializers
+ end
+
+ test "can't serialize unknown object" do
+ assert_raises ActiveJob::SerializationError do
+ ActiveJob::Serializers.serialize @value_object
+ end
+ end
+
+ test "will serialize objects with serializers registered" do
+ ActiveJob::Serializers.add_serializers DummySerializer
+
+ assert_equal(
+ { "_aj_serialized" => "SerializersTest::DummySerializer", "value" => 123 },
+ ActiveJob::Serializers.serialize(@value_object)
+ )
+ end
+
+ test "won't deserialize unknown hash" do
+ hash = { "_dummy_serializer" => 123, "_aj_symbol_keys" => [] }
+ error = assert_raises(ArgumentError) do
+ ActiveJob::Serializers.deserialize(hash)
+ end
+ assert_equal(
+ 'Serializer name is not present in the argument: {"_dummy_serializer"=>123, "_aj_symbol_keys"=>[]}',
+ error.message
+ )
+ end
+
+ test "won't deserialize unknown serializer" do
+ hash = { "_aj_serialized" => "DoNotExist", "value" => 123 }
+ error = assert_raises(ArgumentError) do
+ ActiveJob::Serializers.deserialize(hash)
+ end
+ assert_equal(
+ "Serializer DoNotExist is not known",
+ error.message
+ )
+ end
+
+ test "will deserialize know serialized objects" do
+ ActiveJob::Serializers.add_serializers DummySerializer
+ hash = { "_aj_serialized" => "SerializersTest::DummySerializer", "value" => 123 }
+ assert_equal DummyValueObject.new(123), ActiveJob::Serializers.deserialize(hash)
+ end
+
+ test "adds new serializer" do
+ ActiveJob::Serializers.add_serializers DummySerializer
+ assert ActiveJob::Serializers.serializers.include?(DummySerializer)
+ end
+
+ test "can't add serializer with the same key twice" do
+ ActiveJob::Serializers.add_serializers DummySerializer
+ assert_no_difference(-> { ActiveJob::Serializers.serializers.size }) do
+ ActiveJob::Serializers.add_serializers DummySerializer
+ end
+ end
+end
diff --git a/activejob/test/cases/timezones_test.rb b/activejob/test/cases/timezones_test.rb
new file mode 100644
index 0000000000..e2095b020d
--- /dev/null
+++ b/activejob/test/cases/timezones_test.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require "helper"
+require "jobs/timezone_dependent_job"
+
+class TimezonesTest < ActiveSupport::TestCase
+ setup do
+ JobBuffer.clear
+ end
+
+ test "it performs the job in the given timezone" do
+ job = TimezoneDependentJob.new("2018-01-01T00:00:00Z")
+ job.timezone = "London"
+ job.perform_now
+
+ assert_equal "Happy New Year!", JobBuffer.last_value
+
+ job = TimezoneDependentJob.new("2018-01-01T00:00:00Z")
+ job.timezone = "Eastern Time (US & Canada)"
+ job.perform_now
+
+ assert_equal "Just 5 hours to go", JobBuffer.last_value
+ end
+end
diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb
index 32ef485c45..7a95d3d039 100644
--- a/activejob/test/integration/queuing_test.rb
+++ b/activejob/test/integration/queuing_test.rb
@@ -110,6 +110,22 @@ class QueuingTest < ActiveSupport::TestCase
end
end
+ test "current timezone is kept while running perform_later" do
+ skip if adapter_is?(:inline)
+
+ begin
+ current_zone = Time.zone
+ Time.zone = "Hawaii"
+
+ TestJob.perform_later @id
+ wait_for_jobs_to_finish_for(5.seconds)
+ assert job_executed
+ assert_equal "Hawaii", job_executed_in_timezone
+ ensure
+ Time.zone = current_zone
+ end
+ end
+
test "should run job with higher priority first" do
skip unless adapter_is?(:delayed_job, :que)
diff --git a/activejob/test/jobs/retry_job.rb b/activejob/test/jobs/retry_job.rb
index 9aa99d9a21..82b80fe12b 100644
--- a/activejob/test/jobs/retry_job.rb
+++ b/activejob/test/jobs/retry_job.rb
@@ -10,6 +10,7 @@ class ExponentialWaitTenAttemptsError < StandardError; end
class CustomWaitTenAttemptsError < StandardError; end
class CustomCatchError < StandardError; end
class DiscardableError < StandardError; end
+class CustomDiscardableError < StandardError; end
class RetryJob < ActiveJob::Base
retry_on DefaultsError
@@ -19,6 +20,7 @@ class RetryJob < ActiveJob::Base
retry_on CustomWaitTenAttemptsError, wait: ->(executions) { executions * 2 }, attempts: 10
retry_on(CustomCatchError) { |job, exception| JobBuffer.add("Dealt with a job that failed to retry in a custom way after #{job.arguments.second} attempts. Message: #{exception.message}") }
discard_on DiscardableError
+ discard_on(CustomDiscardableError) { |job, exception| JobBuffer.add("Dealt with a job that was discarded in a custom way") }
def perform(raising, attempts)
if executions < attempts
diff --git a/activejob/test/jobs/timezone_dependent_job.rb b/activejob/test/jobs/timezone_dependent_job.rb
new file mode 100644
index 0000000000..41f473d533
--- /dev/null
+++ b/activejob/test/jobs/timezone_dependent_job.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require_relative "../support/job_buffer"
+
+class TimezoneDependentJob < ActiveJob::Base
+ def perform(now)
+ now = now.in_time_zone
+ new_year = localtime(2018, 1, 1)
+
+ if now >= new_year
+ JobBuffer.add("Happy New Year!")
+ else
+ JobBuffer.add("Just #{(new_year - now).div(3600)} hours to go")
+ end
+ end
+
+ private
+
+ def localtime(*args)
+ Time.zone ? Time.zone.local(*args) : Time.utc(*args)
+ end
+end
diff --git a/activejob/test/support/integration/dummy_app_template.rb b/activejob/test/support/integration/dummy_app_template.rb
index 7ea78c3350..b56dd3e591 100644
--- a/activejob/test/support/integration/dummy_app_template.rb
+++ b/activejob/test/support/integration/dummy_app_template.rb
@@ -21,6 +21,7 @@ class TestJob < ActiveJob::Base
File.open(Rails.root.join("tmp/\#{x}.new"), "wb+") do |f|
f.write Marshal.dump({
"locale" => I18n.locale.to_s || "en",
+ "timezone" => Time.zone.try(:name) || "UTC",
"executed_at" => Time.now.to_r
})
end
diff --git a/activejob/test/support/integration/test_case_helpers.rb b/activejob/test/support/integration/test_case_helpers.rb
index f02a32a38e..3d9b265b66 100644
--- a/activejob/test/support/integration/test_case_helpers.rb
+++ b/activejob/test/support/integration/test_case_helpers.rb
@@ -62,4 +62,8 @@ module TestCaseHelpers
def job_executed_in_locale(id = @id)
job_data(id)["locale"]
end
+
+ def job_executed_in_timezone(id = @id)
+ job_data(id)["timezone"]
+ end
end