aboutsummaryrefslogtreecommitdiffstats
path: root/activejob/lib/active_job
diff options
context:
space:
mode:
Diffstat (limited to 'activejob/lib/active_job')
-rw-r--r--activejob/lib/active_job/arguments.rb141
-rw-r--r--activejob/lib/active_job/serializers.rb61
-rw-r--r--activejob/lib/active_job/serializers/array_serializer.rb24
-rw-r--r--activejob/lib/active_job/serializers/base_serializer.rb41
-rw-r--r--activejob/lib/active_job/serializers/duration_serializer.rb4
-rw-r--r--activejob/lib/active_job/serializers/global_id_serializer.rb30
-rw-r--r--activejob/lib/active_job/serializers/hash_serializer.rb60
-rw-r--r--activejob/lib/active_job/serializers/hash_with_indifferent_access_serializer.rb31
-rw-r--r--activejob/lib/active_job/serializers/object_serializer.rb28
-rw-r--r--activejob/lib/active_job/serializers/standard_type_serializer.rb24
10 files changed, 172 insertions, 272 deletions
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb
index 9d47131864..e6ada163e8 100644
--- a/activejob/lib/active_job/arguments.rb
+++ b/activejob/lib/active_job/arguments.rb
@@ -3,6 +3,24 @@
require "active_support/core_ext/hash"
module ActiveJob
+ # Raised when an exception is raised during job arguments deserialization.
+ #
+ # Wraps the original exception raised as +cause+.
+ class DeserializationError < StandardError
+ def initialize #:nodoc:
+ super("Error while trying to deserialize arguments: #{$!.message}")
+ set_backtrace $!.backtrace
+ end
+ 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).
+ # 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:
@@ -13,16 +31,135 @@ module ActiveJob
# as-is. Arrays/Hashes are serialized element by element.
# All other types are serialized using GlobalID.
def serialize(arguments)
- ActiveJob::Serializers.serialize(arguments)
+ arguments.map { |argument| serialize_argument(argument) }
end
# Deserializes a set of arguments. Whitelisted types are returned
# as-is. Arrays/Hashes are deserialized element by element.
# All other types are deserialized using GlobalID.
def deserialize(arguments)
- ActiveJob::Serializers.deserialize(arguments)
+ arguments.map { |argument| deserialize_argument(argument) }
rescue
raise DeserializationError
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
+ # :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
+ when *TYPE_WHITELIST
+ argument
+ when GlobalID::Identification
+ convert_to_global_id_hash(argument)
+ when Array
+ argument.map { |arg| serialize_argument(arg) }
+ when ActiveSupport::HashWithIndifferentAccess
+ result = serialize_hash(argument)
+ result[WITH_INDIFFERENT_ACCESS_KEY] = serialize_argument(true)
+ result
+ when Hash
+ symbol_keys = argument.each_key.grep(Symbol).map(&:to_s)
+ result = serialize_hash(argument)
+ result[SYMBOL_KEYS_KEY] = symbol_keys
+ result
+ else
+ Serializers.serialize(argument)
+ end
+ end
+
+ def deserialize_argument(argument)
+ case argument
+ when String
+ GlobalID::Locator.locate(argument) || argument
+ when *TYPE_WHITELIST
+ argument
+ when Array
+ argument.map { |arg| deserialize_argument(arg) }
+ when Hash
+ if serialized_global_id?(argument)
+ deserialize_global_id argument
+ elsif custom_serialized?(argument)
+ Serializers.deserialize(argument)
+ else
+ deserialize_hash(argument)
+ end
+ else
+ raise ArgumentError, "Can only deserialize primitive arguments: #{argument.inspect}"
+ end
+ end
+
+ def serialized_global_id?(hash)
+ hash.size == 1 && hash.include?(GLOBALID_KEY)
+ end
+
+ def deserialize_global_id(hash)
+ 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)
+ end
+ end
+
+ def deserialize_hash(serialized_hash)
+ result = serialized_hash.transform_values { |v| deserialize_argument(v) }
+ if result.delete(WITH_INDIFFERENT_ACCESS_KEY)
+ result = result.with_indifferent_access
+ elsif symbol_keys = result.delete(SYMBOL_KEYS_KEY)
+ result = transform_symbol_keys(result, symbol_keys)
+ end
+ result
+ end
+
+ def serialize_hash_key(key)
+ case key
+ when *RESERVED_KEYS
+ raise SerializationError.new("Can't serialize a Hash with reserved key #{key.inspect}")
+ when String, Symbol
+ key.to_s
+ else
+ raise SerializationError.new("Only string and symbol hash keys may be serialized as job arguments, but #{key.inspect} is a #{key.class}")
+ end
+ end
+
+ def transform_symbol_keys(hash, symbol_keys)
+ hash.transform_keys do |key|
+ if symbol_keys.include?(key)
+ key.to_sym
+ else
+ key
+ 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/serializers.rb b/activejob/lib/active_job/serializers.rb
index dfd654175d..d9a130fa73 100644
--- a/activejob/lib/active_job/serializers.rb
+++ b/activejob/lib/active_job/serializers.rb
@@ -3,37 +3,13 @@
require "set"
module ActiveJob
- # Raised when an exception is raised during job arguments deserialization.
- #
- # Wraps the original exception raised as +cause+.
- class DeserializationError < StandardError
- def initialize #:nodoc:
- super("Error while trying to deserialize arguments: #{$!.message}")
- set_backtrace $!.backtrace
- end
- 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).
- # 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
-
# 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
extend ActiveSupport::Autoload
extend ActiveSupport::Concern
- autoload :ArraySerializer
- autoload :BaseSerializer
- autoload :GlobalIDSerializer
- autoload :HashWithIndifferentAccessSerializer
- autoload :HashSerializer
autoload :ObjectSerializer
- autoload :StandardTypeSerializer
autoload :SymbolSerializer
autoload :DurationSerializer
autoload :DateSerializer
@@ -57,8 +33,12 @@ module ActiveJob
# Will look up through all known serializers.
# If no serializers found will raise `ArgumentError`
def deserialize(argument)
- serializer = serializers.detect { |s| s.deserialize?(argument) }
- raise ArgumentError, "Can only deserialize primitive arguments: #{argument.inspect}" unless serializer
+ 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 know" unless serializer
+
serializer.deserialize(argument)
end
@@ -71,36 +51,9 @@ module ActiveJob
def add_serializers(*new_serializers)
self._additional_serializers += new_serializers
end
-
- # Returns a list of reserved keys, which cannot be used as keys for a hash
- def reserved_serializers_keys
- RESERVED_KEYS
- end
end
- # :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
- # :nodoc:
- OBJECT_SERIALIZER_KEY = "_aj_serialized"
-
- # :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
-
- add_serializers GlobalIDSerializer,
- StandardTypeSerializer,
- HashWithIndifferentAccessSerializer,
- HashSerializer,
- ArraySerializer,
- SymbolSerializer,
+ add_serializers SymbolSerializer,
DurationSerializer,
DateTimeSerializer,
DateSerializer,
diff --git a/activejob/lib/active_job/serializers/array_serializer.rb b/activejob/lib/active_job/serializers/array_serializer.rb
deleted file mode 100644
index 9db4edea99..0000000000
--- a/activejob/lib/active_job/serializers/array_serializer.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveJob
- module Serializers
- # Provides methods to serialize and deserialize `Array`
- class ArraySerializer < BaseSerializer # :nodoc:
- alias_method :deserialize?, :serialize?
-
- def serialize(array)
- array.map { |arg| Serializers.serialize(arg) }
- end
-
- def deserialize(array)
- array.map { |arg| Serializers.deserialize(arg) }
- end
-
- private
-
- def klass
- Array
- end
- end
- end
-end
diff --git a/activejob/lib/active_job/serializers/base_serializer.rb b/activejob/lib/active_job/serializers/base_serializer.rb
deleted file mode 100644
index 155eeb29c3..0000000000
--- a/activejob/lib/active_job/serializers/base_serializer.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveJob
- module Serializers
- # Implement the basic interface for Active Job arguments serializers.
- class BaseSerializer
- include Singleton
-
- class << self
- delegate :serialize?, :deserialize?, :serialize, :deserialize, to: :instance
- end
-
- # Determines if an argument should be serialized by a serializer.
- def serialize?(argument)
- argument.is_a?(klass)
- end
-
- # Determines if an argument should be deserialized by a serializer.
- def deserialize?(_argument)
- raise NotImplementedError
- end
-
- # Serializes an argument to a JSON primitive type.
- def serialize(_argument)
- raise NotImplementedError
- end
-
- # Deserilizes an argument form a JSON primiteve type.
- def deserialize(_argument)
- raise NotImplementedError
- end
-
- protected
-
- # The class of the object that will be serialized.
- def klass
- raise NotImplementedError
- end
- end
- end
-end
diff --git a/activejob/lib/active_job/serializers/duration_serializer.rb b/activejob/lib/active_job/serializers/duration_serializer.rb
index a3c4c5d1c2..715fe27a5c 100644
--- a/activejob/lib/active_job/serializers/duration_serializer.rb
+++ b/activejob/lib/active_job/serializers/duration_serializer.rb
@@ -4,12 +4,12 @@ module ActiveJob
module Serializers
class DurationSerializer < ObjectSerializer # :nodoc:
def serialize(duration)
- super("value" => duration.value, "parts" => Serializers.serialize(duration.parts))
+ super("value" => duration.value, "parts" => Arguments.serialize(duration.parts))
end
def deserialize(hash)
value = hash["value"]
- parts = Serializers.deserialize(hash["parts"])
+ parts = Arguments.deserialize(hash["parts"])
klass.new(value, parts)
end
diff --git a/activejob/lib/active_job/serializers/global_id_serializer.rb b/activejob/lib/active_job/serializers/global_id_serializer.rb
deleted file mode 100644
index 2d8629ef02..0000000000
--- a/activejob/lib/active_job/serializers/global_id_serializer.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveJob
- module Serializers
- # Provides methods to serialize and deserialize objects which mixes `GlobalID::Identification`,
- # including `ActiveRecord::Base` models
- class GlobalIDSerializer < BaseSerializer # :nodoc:
- def serialize(object)
- { GLOBALID_KEY => object.to_global_id.to_s }
- rescue URI::GID::MissingModelIdError
- raise SerializationError, "Unable to serialize #{object.class} " \
- "without an id. (Maybe you forgot to call save?)"
- end
-
- def deserialize(hash)
- GlobalID::Locator.locate(hash[GLOBALID_KEY])
- end
-
- def deserialize?(argument)
- argument.is_a?(Hash) && argument[GLOBALID_KEY]
- end
-
- private
-
- def klass
- GlobalID::Identification
- end
- end
- end
-end
diff --git a/activejob/lib/active_job/serializers/hash_serializer.rb b/activejob/lib/active_job/serializers/hash_serializer.rb
deleted file mode 100644
index e569fe7501..0000000000
--- a/activejob/lib/active_job/serializers/hash_serializer.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveJob
- module Serializers
- # Provides methods to serialize and deserialize `Hash` (`{key: field, ...}`)
- # Only `String` or `Symbol` can be used as a key. Values will be serialized by known serializers
- class HashSerializer < BaseSerializer # :nodoc:
- def serialize(hash)
- symbol_keys = hash.each_key.grep(Symbol).map(&:to_s)
- result = serialize_hash(hash)
- result[key] = symbol_keys
- result
- end
-
- def deserialize?(argument)
- argument.is_a?(Hash) && argument[key]
- end
-
- def deserialize(hash)
- result = hash.transform_values { |v| Serializers::deserialize(v) }
- symbol_keys = result.delete(key)
- transform_symbol_keys(result, symbol_keys)
- end
-
- private
-
- def key
- SYMBOL_KEYS_KEY
- end
-
- def serialize_hash(hash)
- hash.each_with_object({}) do |(key, value), result|
- result[serialize_hash_key(key)] = Serializers.serialize(value)
- end
- end
-
- def serialize_hash_key(key)
- raise SerializationError.new("Only string and symbol hash keys may be serialized as job arguments, but #{key.inspect} is a #{key.class}") unless [String, Symbol].include?(key.class)
-
- raise SerializationError.new("Can't serialize a Hash with reserved key #{key.inspect}") if ActiveJob::Serializers.reserved_serializers_keys.include?(key.to_s)
-
- key.to_s
- end
-
- def transform_symbol_keys(hash, symbol_keys)
- hash.transform_keys do |key|
- if symbol_keys.include?(key)
- key.to_sym
- else
- key
- end
- end
- end
-
- def klass
- Hash
- end
- end
- end
-end
diff --git a/activejob/lib/active_job/serializers/hash_with_indifferent_access_serializer.rb b/activejob/lib/active_job/serializers/hash_with_indifferent_access_serializer.rb
deleted file mode 100644
index 3b812ba304..0000000000
--- a/activejob/lib/active_job/serializers/hash_with_indifferent_access_serializer.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveJob
- module Serializers
- # Provides methods to serialize and deserialize `ActiveSupport::HashWithIndifferentAccess`
- # Values will be serialized by known serializers
- class HashWithIndifferentAccessSerializer < HashSerializer # :nodoc:
- def serialize(hash)
- result = serialize_hash(hash)
- result[key] = Serializers.serialize(true)
- result
- end
-
- def deserialize(hash)
- result = hash.transform_values { |v| Serializers.deserialize(v) }
- result.delete(key)
- result.with_indifferent_access
- end
-
- private
-
- def key
- WITH_INDIFFERENT_ACCESS_KEY
- end
-
- def klass
- ActiveSupport::HashWithIndifferentAccess
- end
- end
- end
-end
diff --git a/activejob/lib/active_job/serializers/object_serializer.rb b/activejob/lib/active_job/serializers/object_serializer.rb
index 940b6ff95d..9f59e8236f 100644
--- a/activejob/lib/active_job/serializers/object_serializer.rb
+++ b/activejob/lib/active_job/serializers/object_serializer.rb
@@ -21,14 +21,34 @@ module ActiveJob
# Money
# end
# end
- class ObjectSerializer < BaseSerializer
+ 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)
- { OBJECT_SERIALIZER_KEY => self.class.name }.merge!(hash)
+ { Arguments::OBJECT_SERIALIZER_KEY => self.class.name }.merge!(hash)
end
- def deserialize?(argument)
- argument.is_a?(Hash) && argument[OBJECT_SERIALIZER_KEY] == self.class.name
+ # Deserilizes an argument form a JSON primiteve type.
+ def deserialize(_argument)
+ raise NotImplementedError
end
+
+ protected
+
+ # The class of the object that will be serialized.
+ def klass
+ raise NotImplementedError
+ end
end
end
end
diff --git a/activejob/lib/active_job/serializers/standard_type_serializer.rb b/activejob/lib/active_job/serializers/standard_type_serializer.rb
deleted file mode 100644
index 1db4f3937d..0000000000
--- a/activejob/lib/active_job/serializers/standard_type_serializer.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveJob
- module Serializers
- # Provides methods to serialize and deserialize standard types
- # (`NilClass`, `String`, `Integer`, `Fixnum`, `Bignum`, `Float`, `BigDecimal`, `TrueClass`, `FalseClass`)
- class StandardTypeSerializer < BaseSerializer # :nodoc:
- def serialize?(argument)
- Arguments::TYPE_WHITELIST.include? argument.class
- end
-
- def serialize(argument)
- argument
- end
-
- alias_method :deserialize?, :serialize?
-
- def deserialize(argument)
- object = GlobalID::Locator.locate(argument) if argument.is_a? String
- object || argument
- end
- end
- end
-end