From e360ac12315ed6b9eadca5bcc0d95dc766ba8523 Mon Sep 17 00:00:00 2001 From: Evgenii Pecherkin Date: Tue, 17 Oct 2017 16:05:05 +0400 Subject: Introduce serializers to ActiveJob --- activejob/lib/active_job.rb | 1 + activejob/lib/active_job/arguments.rb | 132 +-------------------- activejob/lib/active_job/base.rb | 2 + activejob/lib/active_job/serializers.rb | 109 +++++++++++++++++ .../lib/active_job/serializers/array_serializer.rb | 26 ++++ .../lib/active_job/serializers/base_serializer.rb | 13 ++ .../lib/active_job/serializers/class_serializer.rb | 24 ++++ .../active_job/serializers/duration_serializer.rb | 42 +++++++ .../active_job/serializers/global_id_serializer.rb | 32 +++++ .../lib/active_job/serializers/hash_serializer.rb | 62 ++++++++++ .../hash_with_indifferent_access_serializer.rb | 37 ++++++ .../active_job/serializers/object_serializer.rb | 27 +++++ .../serializers/standard_type_serializer.rb | 26 ++++ .../active_job/serializers/struct_serializer.rb | 38 ++++++ .../active_job/serializers/symbol_serializer.rb | 28 +++++ .../lib/rails/generators/job/job_generator.rb | 2 +- 16 files changed, 470 insertions(+), 131 deletions(-) create mode 100644 activejob/lib/active_job/serializers.rb create mode 100644 activejob/lib/active_job/serializers/array_serializer.rb create mode 100644 activejob/lib/active_job/serializers/base_serializer.rb create mode 100644 activejob/lib/active_job/serializers/class_serializer.rb create mode 100644 activejob/lib/active_job/serializers/duration_serializer.rb create mode 100644 activejob/lib/active_job/serializers/global_id_serializer.rb create mode 100644 activejob/lib/active_job/serializers/hash_serializer.rb create mode 100644 activejob/lib/active_job/serializers/hash_with_indifferent_access_serializer.rb create mode 100644 activejob/lib/active_job/serializers/object_serializer.rb create mode 100644 activejob/lib/active_job/serializers/standard_type_serializer.rb create mode 100644 activejob/lib/active_job/serializers/struct_serializer.rb create mode 100644 activejob/lib/active_job/serializers/symbol_serializer.rb (limited to 'activejob/lib') 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..9d47131864 100644 --- a/activejob/lib/active_job/arguments.rb +++ b/activejob/lib/active_job/arguments.rb @@ -3,24 +3,6 @@ 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: @@ -31,126 +13,16 @@ module ActiveJob # as-is. Arrays/Hashes are serialized element by element. # All other types are serialized using GlobalID. def serialize(arguments) - arguments.map { |argument| serialize_argument(argument) } + ActiveJob::Serializers.serialize(arguments) 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) - arguments.map { |argument| deserialize_argument(argument) } + ActiveJob::Serializers.deserialize(arguments) 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 - private_constant :GLOBALID_KEY, :SYMBOL_KEYS_KEY, :WITH_INDIFFERENT_ACCESS_KEY - - 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 - raise SerializationError.new("Unsupported argument type: #{argument.class.name}") - 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 - 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 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 - - # :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 - 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/base.rb b/activejob/lib/active_job/base.rb index ae112abb2c..8275776820 100644 --- a/activejob/lib/active_job/base.rb +++ b/activejob/lib/active_job/base.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "active_job/core" +require "active_job/serializers" require "active_job/queue_adapter" require "active_job/queue_name" require "active_job/queue_priority" @@ -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 diff --git a/activejob/lib/active_job/serializers.rb b/activejob/lib/active_job/serializers.rb new file mode 100644 index 0000000000..ec86065149 --- /dev/null +++ b/activejob/lib/active_job/serializers.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +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 ActiveJob::Serializers 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 :ClassSerializer + autoload :DurationSerializer + autoload :GlobalIDSerializer + autoload :HashWithIndifferentAccessSerializer + autoload :HashSerializer + autoload :ObjectSerializer + autoload :StandardTypeSerializer + autoload :StructSerializer + autoload :SymbolSerializer + + included do + class_attribute :_additional_serializers, instance_accessor: false, instance_predicate: false + self._additional_serializers = [] + end + + # Includes the method to list known serializers and to add new ones + module ClassMethods + # Returns list of known serializers + def serializers + self._additional_serializers + SERIALIZERS + end + + # Adds a new serializer to a list of known serializers + def add_serializers(*serializers) + check_duplicate_serializer_keys!(serializers) + + @_additional_serializers = serializers + @_additional_serializers + end + + # Returns a list of reserved keys, which cannot be used as keys for a hash + def reserved_serializers_keys + serializers.select { |s| s.respond_to?(:key) }.map(&:key) + end + + private + + def check_duplicate_serializer_keys!(serializers) + keys_to_add = serializers.select { |s| s.respond_to?(:key) }.map(&:key) + + duplicate_keys = reserved_keys & keys_to_add + + raise ArgumentError.new("Can't add serializers because of keys duplication: #{duplicate_keys}") if duplicate_keys.any? + end + end + + # :nodoc: + SERIALIZERS = [ + ::ActiveJob::Serializers::GlobalIDSerializer, + ::ActiveJob::Serializers::DurationSerializer, + ::ActiveJob::Serializers::StructSerializer, + ::ActiveJob::Serializers::SymbolSerializer, + ::ActiveJob::Serializers::ClassSerializer, + ::ActiveJob::Serializers::StandardTypeSerializer, + ::ActiveJob::Serializers::HashWithIndifferentAccessSerializer, + ::ActiveJob::Serializers::HashSerializer, + ::ActiveJob::Serializers::ArraySerializer + ].freeze + private_constant :SERIALIZERS + + class << self + # Returns serialized representative of the passed object. + # Will look up through all known serializers. + # Raises `SerializationError` if it can't find a proper serializer. + def serialize(argument) + serializer = ::ActiveJob::Base.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 serializers found will raise `ArgumentError` + def deserialize(argument) + serializer = ::ActiveJob::Base.serializers.detect { |s| s.deserialize?(argument) } + raise ArgumentError, "Can only deserialize primitive arguments: #{argument.inspect}" unless serializer + serializer.deserialize(argument) + end + end + end +end diff --git a/activejob/lib/active_job/serializers/array_serializer.rb b/activejob/lib/active_job/serializers/array_serializer.rb new file mode 100644 index 0000000000..f0254f4488 --- /dev/null +++ b/activejob/lib/active_job/serializers/array_serializer.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module ActiveJob + module Serializers + # Provides methods to serialize and deserialize `Array` + class ArraySerializer < BaseSerializer + class << self + alias_method :deserialize?, :serialize? + + def serialize(array) + array.map { |arg| ::ActiveJob::Serializers.serialize(arg) } + end + + def deserialize(array) + array.map { |arg| ::ActiveJob::Serializers.deserialize(arg) } + end + + private + + def klass + ::Array + end + end + end + end +end diff --git a/activejob/lib/active_job/serializers/base_serializer.rb b/activejob/lib/active_job/serializers/base_serializer.rb new file mode 100644 index 0000000000..98f7852fd6 --- /dev/null +++ b/activejob/lib/active_job/serializers/base_serializer.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module ActiveJob + module Serializers + class BaseSerializer + class << self + def serialize?(argument) + argument.is_a?(klass) + end + end + end + end +end diff --git a/activejob/lib/active_job/serializers/class_serializer.rb b/activejob/lib/active_job/serializers/class_serializer.rb new file mode 100644 index 0000000000..d36e8c0ebc --- /dev/null +++ b/activejob/lib/active_job/serializers/class_serializer.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module ActiveJob + module Serializers + # Provides methods to serialize and deserialize `Class` (`ActiveRecord::Base`, `MySpecialService`, ...) + class ClassSerializer < ObjectSerializer + class << self + def serialize(argument_klass) + { key => "::#{argument_klass.name}" } + end + + def key + "_aj_class" + end + + private + + def klass + ::Class + end + 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..72b7b9528a --- /dev/null +++ b/activejob/lib/active_job/serializers/duration_serializer.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module ActiveJob + module Serializers + # Provides methods to serialize and deserialize `ActiveSupport::Duration` (`1.day`, `2.weeks`, ...) + class DurationSerializer < ObjectSerializer + class << self + def serialize(duration) + { + key => duration.value, + parts_key => ::ActiveJob::Serializers.serialize(duration.parts) + } + end + + def deserialize(hash) + value = hash[key] + parts = ::ActiveJob::Serializers.deserialize(hash[parts_key]) + + klass.new(value, parts) + end + + def key + "_aj_activesupport_duration" + end + + private + + def klass + ::ActiveSupport::Duration + end + + def keys + super.push parts_key + end + + def parts_key + "parts" + end + end + end + end +end diff --git a/activejob/lib/active_job/serializers/global_id_serializer.rb b/activejob/lib/active_job/serializers/global_id_serializer.rb new file mode 100644 index 0000000000..1961e43fca --- /dev/null +++ b/activejob/lib/active_job/serializers/global_id_serializer.rb @@ -0,0 +1,32 @@ +# 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 < ObjectSerializer + class << self + def serialize(object) + { 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[key]) + end + + def key + "_aj_globalid" + end + + private + + def klass + ::GlobalID::Identification + end + end + end + end +end diff --git a/activejob/lib/active_job/serializers/hash_serializer.rb b/activejob/lib/active_job/serializers/hash_serializer.rb new file mode 100644 index 0000000000..eee081de7c --- /dev/null +++ b/activejob/lib/active_job/serializers/hash_serializer.rb @@ -0,0 +1,62 @@ +# 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 + class << self + 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| ::ActiveJob::Serializers::deserialize(v) } + symbol_keys = result.delete(key) + transform_symbol_keys(result, symbol_keys) + end + + def key + "_aj_symbol_keys" + end + + private + + def serialize_hash(hash) + hash.each_with_object({}) do |(key, value), result| + result[serialize_hash_key(key)] = ::ActiveJob::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::Base.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 +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 new file mode 100644 index 0000000000..50e80757cd --- /dev/null +++ b/activejob/lib/active_job/serializers/hash_with_indifferent_access_serializer.rb @@ -0,0 +1,37 @@ +# 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 + class << self + def serialize(hash) + result = serialize_hash(hash) + result[key] = ::ActiveJob::Serializers.serialize(true) + result + end + + def deserialize?(argument) + argument.is_a?(Hash) && argument[key] + end + + def deserialize(hash) + result = hash.transform_values { |v| ::ActiveJob::Serializers.deserialize(v) } + result.delete(key) + result.with_indifferent_access + end + + def key + "_aj_hash_with_indifferent_access" + end + + private + + def klass + ::ActiveSupport::HashWithIndifferentAccess + end + 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..075360b26e --- /dev/null +++ b/activejob/lib/active_job/serializers/object_serializer.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module ActiveJob + module Serializers + class ObjectSerializer < BaseSerializer + class << self + def serialize(object) + { key => object.class.name } + end + + def deserialize?(argument) + argument.respond_to?(:keys) && argument.keys == keys + end + + def deserialize(hash) + hash[key].constantize + end + + private + + def keys + [key] + end + 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 new file mode 100644 index 0000000000..8969b31d6b --- /dev/null +++ b/activejob/lib/active_job/serializers/standard_type_serializer.rb @@ -0,0 +1,26 @@ +# 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 + class << self + def serialize?(argument) + ::ActiveJob::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 +end diff --git a/activejob/lib/active_job/serializers/struct_serializer.rb b/activejob/lib/active_job/serializers/struct_serializer.rb new file mode 100644 index 0000000000..f6791611ed --- /dev/null +++ b/activejob/lib/active_job/serializers/struct_serializer.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module ActiveJob + module Serializers + # Provides methods to serialize and deserialize struct instances + # (`Struct.new('Rectangle', :width, :height).new(12, 20)`) + class StructSerializer < ObjectSerializer + class << self + def serialize(object) + super.merge values_key => ::ActiveJob::Serializers.serialize(object.values) + end + + def deserialize(hash) + values = ::ActiveJob::Serializers.deserialize(hash[values_key]) + super.new(*values) + end + + def key + "_aj_struct" + end + + private + + def klass + ::Struct + end + + def keys + super.push values_key + end + + def values_key + "values" + end + 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..f128ae8284 --- /dev/null +++ b/activejob/lib/active_job/serializers/symbol_serializer.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module ActiveJob + module Serializers + # Provides methods to serialize and deserialize `Symbol` (`:foo`, `:bar`, ...) + class SymbolSerializer < ObjectSerializer + class << self + def serialize(symbol) + { key => symbol.to_s } + end + + def deserialize(hash) + hash[key].to_sym + end + + def key + "_aj_symbol" + end + + private + + def klass + ::Symbol + end + end + end + end +end diff --git a/activejob/lib/rails/generators/job/job_generator.rb b/activejob/lib/rails/generators/job/job_generator.rb index 69b4fe7d26..c940cd154c 100644 --- a/activejob/lib/rails/generators/job/job_generator.rb +++ b/activejob/lib/rails/generators/job/job_generator.rb @@ -30,7 +30,7 @@ module Rails # :nodoc: private def application_job_file_name @application_job_file_name ||= if mountable_engine? - "app/jobs/#{namespaced_path}/application_job.rb" + "app/jobs/#{namespaced_path}/application_job.rb" else "app/jobs/application_job.rb" end -- cgit v1.2.3 From 3785a5729959a838bb13f2d298a59e12e1844f74 Mon Sep 17 00:00:00 2001 From: Evgenii Pecherkin Date: Mon, 23 Oct 2017 17:29:28 +0400 Subject: Remove non-default serializers --- activejob/lib/active_job/serializers.rb | 21 ++++------- .../lib/active_job/serializers/class_serializer.rb | 24 ------------- .../active_job/serializers/duration_serializer.rb | 42 ---------------------- .../active_job/serializers/struct_serializer.rb | 38 -------------------- .../active_job/serializers/symbol_serializer.rb | 28 --------------- .../lib/rails/generators/job/job_generator.rb | 2 +- 6 files changed, 7 insertions(+), 148 deletions(-) delete mode 100644 activejob/lib/active_job/serializers/class_serializer.rb delete mode 100644 activejob/lib/active_job/serializers/duration_serializer.rb delete mode 100644 activejob/lib/active_job/serializers/struct_serializer.rb delete mode 100644 activejob/lib/active_job/serializers/symbol_serializer.rb (limited to 'activejob/lib') diff --git a/activejob/lib/active_job/serializers.rb b/activejob/lib/active_job/serializers.rb index ec86065149..68ed94896e 100644 --- a/activejob/lib/active_job/serializers.rb +++ b/activejob/lib/active_job/serializers.rb @@ -27,15 +27,11 @@ module ActiveJob autoload :ArraySerializer autoload :BaseSerializer - autoload :ClassSerializer - autoload :DurationSerializer autoload :GlobalIDSerializer autoload :HashWithIndifferentAccessSerializer autoload :HashSerializer autoload :ObjectSerializer autoload :StandardTypeSerializer - autoload :StructSerializer - autoload :SymbolSerializer included do class_attribute :_additional_serializers, instance_accessor: false, instance_predicate: false @@ -46,14 +42,14 @@ module ActiveJob module ClassMethods # Returns list of known serializers def serializers - self._additional_serializers + SERIALIZERS + self._additional_serializers + ActiveJob::Serializers::SERIALIZERS end # Adds a new serializer to a list of known serializers - def add_serializers(*serializers) - check_duplicate_serializer_keys!(serializers) + def add_serializers(*new_serializers) + check_duplicate_serializer_keys!(new_serializers) - @_additional_serializers = serializers + @_additional_serializers + self._additional_serializers = new_serializers + self._additional_serializers end # Returns a list of reserved keys, which cannot be used as keys for a hash @@ -66,7 +62,7 @@ module ActiveJob def check_duplicate_serializer_keys!(serializers) keys_to_add = serializers.select { |s| s.respond_to?(:key) }.map(&:key) - duplicate_keys = reserved_keys & keys_to_add + duplicate_keys = reserved_serializers_keys & keys_to_add raise ArgumentError.new("Can't add serializers because of keys duplication: #{duplicate_keys}") if duplicate_keys.any? end @@ -75,21 +71,16 @@ module ActiveJob # :nodoc: SERIALIZERS = [ ::ActiveJob::Serializers::GlobalIDSerializer, - ::ActiveJob::Serializers::DurationSerializer, - ::ActiveJob::Serializers::StructSerializer, - ::ActiveJob::Serializers::SymbolSerializer, - ::ActiveJob::Serializers::ClassSerializer, ::ActiveJob::Serializers::StandardTypeSerializer, ::ActiveJob::Serializers::HashWithIndifferentAccessSerializer, ::ActiveJob::Serializers::HashSerializer, ::ActiveJob::Serializers::ArraySerializer ].freeze - private_constant :SERIALIZERS class << self # Returns serialized representative of the passed object. # Will look up through all known serializers. - # Raises `SerializationError` if it can't find a proper serializer. + # Raises `ActiveJob::SerializationError` if it can't find a proper serializer. def serialize(argument) serializer = ::ActiveJob::Base.serializers.detect { |s| s.serialize?(argument) } raise SerializationError.new("Unsupported argument type: #{argument.class.name}") unless serializer diff --git a/activejob/lib/active_job/serializers/class_serializer.rb b/activejob/lib/active_job/serializers/class_serializer.rb deleted file mode 100644 index d36e8c0ebc..0000000000 --- a/activejob/lib/active_job/serializers/class_serializer.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module ActiveJob - module Serializers - # Provides methods to serialize and deserialize `Class` (`ActiveRecord::Base`, `MySpecialService`, ...) - class ClassSerializer < ObjectSerializer - class << self - def serialize(argument_klass) - { key => "::#{argument_klass.name}" } - end - - def key - "_aj_class" - end - - private - - def klass - ::Class - end - end - end - end -end diff --git a/activejob/lib/active_job/serializers/duration_serializer.rb b/activejob/lib/active_job/serializers/duration_serializer.rb deleted file mode 100644 index 72b7b9528a..0000000000 --- a/activejob/lib/active_job/serializers/duration_serializer.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -module ActiveJob - module Serializers - # Provides methods to serialize and deserialize `ActiveSupport::Duration` (`1.day`, `2.weeks`, ...) - class DurationSerializer < ObjectSerializer - class << self - def serialize(duration) - { - key => duration.value, - parts_key => ::ActiveJob::Serializers.serialize(duration.parts) - } - end - - def deserialize(hash) - value = hash[key] - parts = ::ActiveJob::Serializers.deserialize(hash[parts_key]) - - klass.new(value, parts) - end - - def key - "_aj_activesupport_duration" - end - - private - - def klass - ::ActiveSupport::Duration - end - - def keys - super.push parts_key - end - - def parts_key - "parts" - end - end - end - end -end diff --git a/activejob/lib/active_job/serializers/struct_serializer.rb b/activejob/lib/active_job/serializers/struct_serializer.rb deleted file mode 100644 index f6791611ed..0000000000 --- a/activejob/lib/active_job/serializers/struct_serializer.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -module ActiveJob - module Serializers - # Provides methods to serialize and deserialize struct instances - # (`Struct.new('Rectangle', :width, :height).new(12, 20)`) - class StructSerializer < ObjectSerializer - class << self - def serialize(object) - super.merge values_key => ::ActiveJob::Serializers.serialize(object.values) - end - - def deserialize(hash) - values = ::ActiveJob::Serializers.deserialize(hash[values_key]) - super.new(*values) - end - - def key - "_aj_struct" - end - - private - - def klass - ::Struct - end - - def keys - super.push values_key - end - - def values_key - "values" - end - end - end - end -end diff --git a/activejob/lib/active_job/serializers/symbol_serializer.rb b/activejob/lib/active_job/serializers/symbol_serializer.rb deleted file mode 100644 index f128ae8284..0000000000 --- a/activejob/lib/active_job/serializers/symbol_serializer.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -module ActiveJob - module Serializers - # Provides methods to serialize and deserialize `Symbol` (`:foo`, `:bar`, ...) - class SymbolSerializer < ObjectSerializer - class << self - def serialize(symbol) - { key => symbol.to_s } - end - - def deserialize(hash) - hash[key].to_sym - end - - def key - "_aj_symbol" - end - - private - - def klass - ::Symbol - end - end - end - end -end diff --git a/activejob/lib/rails/generators/job/job_generator.rb b/activejob/lib/rails/generators/job/job_generator.rb index c940cd154c..69b4fe7d26 100644 --- a/activejob/lib/rails/generators/job/job_generator.rb +++ b/activejob/lib/rails/generators/job/job_generator.rb @@ -30,7 +30,7 @@ module Rails # :nodoc: private def application_job_file_name @application_job_file_name ||= if mountable_engine? - "app/jobs/#{namespaced_path}/application_job.rb" + "app/jobs/#{namespaced_path}/application_job.rb" else "app/jobs/application_job.rb" end -- cgit v1.2.3 From ec686a471e0a54194fc9ec72e639785606597704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 9 Feb 2018 14:24:55 -0500 Subject: Simplify the implementation of custom serialziers Right now it is only possible to define serializers globally so we don't need to use a class attribute in the job class. --- activejob/lib/active_job/serializers.rb | 62 ++++++++++------------ .../lib/active_job/serializers/hash_serializer.rb | 2 +- 2 files changed, 28 insertions(+), 36 deletions(-) (limited to 'activejob/lib') diff --git a/activejob/lib/active_job/serializers.rb b/activejob/lib/active_job/serializers.rb index 68ed94896e..41113c521c 100644 --- a/activejob/lib/active_job/serializers.rb +++ b/activejob/lib/active_job/serializers.rb @@ -33,16 +33,31 @@ module ActiveJob autoload :ObjectSerializer autoload :StandardTypeSerializer - included do - class_attribute :_additional_serializers, instance_accessor: false, instance_predicate: false - self._additional_serializers = [] - end + mattr_accessor :_additional_serializers + self._additional_serializers = [] + + class << self + # Returns serialized representative of the passed object. + # Will look up through all known serializers. + # Raises `ActiveJob::SerializationError` 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 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.deserialize(argument) + end - # Includes the method to list known serializers and to add new ones - module ClassMethods # Returns list of known serializers def serializers - self._additional_serializers + ActiveJob::Serializers::SERIALIZERS + self._additional_serializers end # Adds a new serializer to a list of known serializers @@ -68,33 +83,10 @@ module ActiveJob end end - # :nodoc: - SERIALIZERS = [ - ::ActiveJob::Serializers::GlobalIDSerializer, - ::ActiveJob::Serializers::StandardTypeSerializer, - ::ActiveJob::Serializers::HashWithIndifferentAccessSerializer, - ::ActiveJob::Serializers::HashSerializer, - ::ActiveJob::Serializers::ArraySerializer - ].freeze - - class << self - # Returns serialized representative of the passed object. - # Will look up through all known serializers. - # Raises `ActiveJob::SerializationError` if it can't find a proper serializer. - def serialize(argument) - serializer = ::ActiveJob::Base.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 serializers found will raise `ArgumentError` - def deserialize(argument) - serializer = ::ActiveJob::Base.serializers.detect { |s| s.deserialize?(argument) } - raise ArgumentError, "Can only deserialize primitive arguments: #{argument.inspect}" unless serializer - serializer.deserialize(argument) - end - end + add_serializers GlobalIDSerializer, + StandardTypeSerializer, + HashWithIndifferentAccessSerializer, + HashSerializer, + ArraySerializer end end diff --git a/activejob/lib/active_job/serializers/hash_serializer.rb b/activejob/lib/active_job/serializers/hash_serializer.rb index eee081de7c..c4dcfaf094 100644 --- a/activejob/lib/active_job/serializers/hash_serializer.rb +++ b/activejob/lib/active_job/serializers/hash_serializer.rb @@ -38,7 +38,7 @@ module ActiveJob 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::Base.reserved_serializers_keys.include?(key.to_s) + 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 -- cgit v1.2.3 From 803f4385c6c30217e3d2cf81cbaba92c7bc58476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 9 Feb 2018 14:31:00 -0500 Subject: Remove unnecessary qualified constant lookups --- activejob/lib/active_job/serializers/array_serializer.rb | 6 +++--- activejob/lib/active_job/serializers/global_id_serializer.rb | 2 +- activejob/lib/active_job/serializers/hash_serializer.rb | 6 +++--- .../serializers/hash_with_indifferent_access_serializer.rb | 6 +++--- activejob/lib/active_job/serializers/standard_type_serializer.rb | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) (limited to 'activejob/lib') diff --git a/activejob/lib/active_job/serializers/array_serializer.rb b/activejob/lib/active_job/serializers/array_serializer.rb index f0254f4488..1b3c3b2ce3 100644 --- a/activejob/lib/active_job/serializers/array_serializer.rb +++ b/activejob/lib/active_job/serializers/array_serializer.rb @@ -8,17 +8,17 @@ module ActiveJob alias_method :deserialize?, :serialize? def serialize(array) - array.map { |arg| ::ActiveJob::Serializers.serialize(arg) } + array.map { |arg| Serializers.serialize(arg) } end def deserialize(array) - array.map { |arg| ::ActiveJob::Serializers.deserialize(arg) } + array.map { |arg| Serializers.deserialize(arg) } end private def klass - ::Array + Array end end end diff --git a/activejob/lib/active_job/serializers/global_id_serializer.rb b/activejob/lib/active_job/serializers/global_id_serializer.rb index 1961e43fca..ec20cf04f7 100644 --- a/activejob/lib/active_job/serializers/global_id_serializer.rb +++ b/activejob/lib/active_job/serializers/global_id_serializer.rb @@ -24,7 +24,7 @@ module ActiveJob private def klass - ::GlobalID::Identification + GlobalID::Identification end end end diff --git a/activejob/lib/active_job/serializers/hash_serializer.rb b/activejob/lib/active_job/serializers/hash_serializer.rb index c4dcfaf094..ca39a81ae9 100644 --- a/activejob/lib/active_job/serializers/hash_serializer.rb +++ b/activejob/lib/active_job/serializers/hash_serializer.rb @@ -18,7 +18,7 @@ module ActiveJob end def deserialize(hash) - result = hash.transform_values { |v| ::ActiveJob::Serializers::deserialize(v) } + result = hash.transform_values { |v| Serializers::deserialize(v) } symbol_keys = result.delete(key) transform_symbol_keys(result, symbol_keys) end @@ -31,7 +31,7 @@ module ActiveJob def serialize_hash(hash) hash.each_with_object({}) do |(key, value), result| - result[serialize_hash_key(key)] = ::ActiveJob::Serializers.serialize(value) + result[serialize_hash_key(key)] = Serializers.serialize(value) end end @@ -54,7 +54,7 @@ module ActiveJob end def klass - ::Hash + Hash 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 index 50e80757cd..b0fb29d58b 100644 --- a/activejob/lib/active_job/serializers/hash_with_indifferent_access_serializer.rb +++ b/activejob/lib/active_job/serializers/hash_with_indifferent_access_serializer.rb @@ -8,7 +8,7 @@ module ActiveJob class << self def serialize(hash) result = serialize_hash(hash) - result[key] = ::ActiveJob::Serializers.serialize(true) + result[key] = Serializers.serialize(true) result end @@ -17,7 +17,7 @@ module ActiveJob end def deserialize(hash) - result = hash.transform_values { |v| ::ActiveJob::Serializers.deserialize(v) } + result = hash.transform_values { |v| Serializers.deserialize(v) } result.delete(key) result.with_indifferent_access end @@ -29,7 +29,7 @@ module ActiveJob private def klass - ::ActiveSupport::HashWithIndifferentAccess + ActiveSupport::HashWithIndifferentAccess 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 index 8969b31d6b..efc02adcf0 100644 --- a/activejob/lib/active_job/serializers/standard_type_serializer.rb +++ b/activejob/lib/active_job/serializers/standard_type_serializer.rb @@ -7,7 +7,7 @@ module ActiveJob class StandardTypeSerializer < BaseSerializer class << self def serialize?(argument) - ::ActiveJob::Arguments::TYPE_WHITELIST.include? argument.class + Arguments::TYPE_WHITELIST.include? argument.class end def serialize(argument) -- cgit v1.2.3 From 9bc8b4bbde4634e0e4bddcffa25e0bf8d74d19cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 9 Feb 2018 14:34:29 -0500 Subject: Define the interface of a Serializer --- .../lib/active_job/serializers/base_serializer.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'activejob/lib') diff --git a/activejob/lib/active_job/serializers/base_serializer.rb b/activejob/lib/active_job/serializers/base_serializer.rb index 98f7852fd6..8b891cca48 100644 --- a/activejob/lib/active_job/serializers/base_serializer.rb +++ b/activejob/lib/active_job/serializers/base_serializer.rb @@ -7,6 +7,24 @@ module ActiveJob def serialize?(argument) argument.is_a?(klass) end + + def deserialize?(_argument) + raise NotImplementedError + end + + def serialize(_argument) + raise NotImplementedError + end + + def deserialize(_argument) + raise NotImplementedError + end + + private + + def klass + raise NotImplementedError + end end end end -- cgit v1.2.3 From ea615332452e6860872020aa161c5d34e81f1eea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 9 Feb 2018 15:23:05 -0500 Subject: Only add one more custom key in the serialized hash Now custom serialziers can register itself in the serialized hash using the "_aj_serialized" key that constains the serializer name. This way we can avoid poluting the hash with many reserved keys. --- activejob/lib/active_job/serializers.rb | 37 +++++++++++++--------- .../active_job/serializers/global_id_serializer.rb | 10 +++--- .../lib/active_job/serializers/hash_serializer.rb | 6 ++-- .../hash_with_indifferent_access_serializer.rb | 10 ++---- .../active_job/serializers/object_serializer.rb | 16 ++-------- 5 files changed, 36 insertions(+), 43 deletions(-) (limited to 'activejob/lib') diff --git a/activejob/lib/active_job/serializers.rb b/activejob/lib/active_job/serializers.rb index 41113c521c..12458ea572 100644 --- a/activejob/lib/active_job/serializers.rb +++ b/activejob/lib/active_job/serializers.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "set" + module ActiveJob # Raised when an exception is raised during job arguments deserialization. # @@ -34,7 +36,7 @@ module ActiveJob autoload :StandardTypeSerializer mattr_accessor :_additional_serializers - self._additional_serializers = [] + self._additional_serializers = Set.new class << self # Returns serialized representative of the passed object. @@ -62,27 +64,32 @@ module ActiveJob # Adds a new serializer to a list of known serializers def add_serializers(*new_serializers) - check_duplicate_serializer_keys!(new_serializers) - - self._additional_serializers = new_serializers + self._additional_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 - serializers.select { |s| s.respond_to?(:key) }.map(&:key) + RESERVED_KEYS end - - private - - def check_duplicate_serializer_keys!(serializers) - keys_to_add = serializers.select { |s| s.respond_to?(:key) }.map(&:key) - - duplicate_keys = reserved_serializers_keys & keys_to_add - - raise ArgumentError.new("Can't add serializers because of keys duplication: #{duplicate_keys}") if duplicate_keys.any? - 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, diff --git a/activejob/lib/active_job/serializers/global_id_serializer.rb b/activejob/lib/active_job/serializers/global_id_serializer.rb index ec20cf04f7..84ed33ef99 100644 --- a/activejob/lib/active_job/serializers/global_id_serializer.rb +++ b/activejob/lib/active_job/serializers/global_id_serializer.rb @@ -4,21 +4,21 @@ module ActiveJob module Serializers # Provides methods to serialize and deserialize objects which mixes `GlobalID::Identification`, # including `ActiveRecord::Base` models - class GlobalIDSerializer < ObjectSerializer + class GlobalIDSerializer < BaseSerializer class << self def serialize(object) - { key => object.to_global_id.to_s } + { 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[key]) + GlobalID::Locator.locate(hash[GLOBALID_KEY]) end - def key - "_aj_globalid" + def deserialize?(argument) + argument.is_a?(Hash) && argument[GLOBALID_KEY] end private diff --git a/activejob/lib/active_job/serializers/hash_serializer.rb b/activejob/lib/active_job/serializers/hash_serializer.rb index ca39a81ae9..2bbb31946d 100644 --- a/activejob/lib/active_job/serializers/hash_serializer.rb +++ b/activejob/lib/active_job/serializers/hash_serializer.rb @@ -23,12 +23,12 @@ module ActiveJob transform_symbol_keys(result, symbol_keys) end + private + def key - "_aj_symbol_keys" + SYMBOL_KEYS_KEY end - private - def serialize_hash(hash) hash.each_with_object({}) do |(key, value), result| result[serialize_hash_key(key)] = Serializers.serialize(value) 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 index b0fb29d58b..af3576dd57 100644 --- a/activejob/lib/active_job/serializers/hash_with_indifferent_access_serializer.rb +++ b/activejob/lib/active_job/serializers/hash_with_indifferent_access_serializer.rb @@ -12,22 +12,18 @@ module ActiveJob result end - def deserialize?(argument) - argument.is_a?(Hash) && argument[key] - end - def deserialize(hash) result = hash.transform_values { |v| Serializers.deserialize(v) } result.delete(key) result.with_indifferent_access end + private + def key - "_aj_hash_with_indifferent_access" + WITH_INDIFFERENT_ACCESS_KEY end - private - def klass ActiveSupport::HashWithIndifferentAccess end diff --git a/activejob/lib/active_job/serializers/object_serializer.rb b/activejob/lib/active_job/serializers/object_serializer.rb index 075360b26e..d5ff8c91f1 100644 --- a/activejob/lib/active_job/serializers/object_serializer.rb +++ b/activejob/lib/active_job/serializers/object_serializer.rb @@ -4,22 +4,12 @@ module ActiveJob module Serializers class ObjectSerializer < BaseSerializer class << self - def serialize(object) - { key => object.class.name } + def serialize(hash) + { OBJECT_SERIALIZER_KEY => self.name }.merge!(hash) end def deserialize?(argument) - argument.respond_to?(:keys) && argument.keys == keys - end - - def deserialize(hash) - hash[key].constantize - end - - private - - def keys - [key] + argument.is_a?(Hash) && argument[OBJECT_SERIALIZER_KEY] == self.name end end end -- cgit v1.2.3 From b098584f63521d214c1107e6eaa24f292b8e4df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 9 Feb 2018 15:45:11 -0500 Subject: Add symbol and duration serializers --- activejob/lib/active_job/serializers.rb | 6 +++++- .../active_job/serializers/duration_serializer.rb | 24 ++++++++++++++++++++++ .../active_job/serializers/symbol_serializer.rb | 21 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 activejob/lib/active_job/serializers/duration_serializer.rb create mode 100644 activejob/lib/active_job/serializers/symbol_serializer.rb (limited to 'activejob/lib') diff --git a/activejob/lib/active_job/serializers.rb b/activejob/lib/active_job/serializers.rb index 12458ea572..9e3fcda28d 100644 --- a/activejob/lib/active_job/serializers.rb +++ b/activejob/lib/active_job/serializers.rb @@ -34,6 +34,8 @@ module ActiveJob autoload :HashSerializer autoload :ObjectSerializer autoload :StandardTypeSerializer + autoload :SymbolSerializer + autoload :DurationSerializer mattr_accessor :_additional_serializers self._additional_serializers = Set.new @@ -94,6 +96,8 @@ module ActiveJob StandardTypeSerializer, HashWithIndifferentAccessSerializer, HashSerializer, - ArraySerializer + ArraySerializer, + SymbolSerializer, + DurationSerializer 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..46543cc30d --- /dev/null +++ b/activejob/lib/active_job/serializers/duration_serializer.rb @@ -0,0 +1,24 @@ +module ActiveJob + module Serializers + class DurationSerializer < ObjectSerializer + class << self + def serialize(duration) + super("value" => duration.value, "parts" => Serializers.serialize(duration.parts)) + end + + def deserialize(hash) + value = hash["value"] + parts = Serializers.deserialize(hash["parts"]) + + klass.new(value, parts) + end + + private + + def klass + ActiveSupport::Duration + end + 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..ec27f6828a --- /dev/null +++ b/activejob/lib/active_job/serializers/symbol_serializer.rb @@ -0,0 +1,21 @@ +module ActiveJob + module Serializers + class SymbolSerializer < ObjectSerializer + class << self + 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 +end -- cgit v1.2.3 From d2d98d69468bf34a39794496beb8f9f7b69088c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 9 Feb 2018 16:32:35 -0500 Subject: Allow serializers to be used either as classes or objects --- .../lib/active_job/serializers/array_serializer.rb | 20 ++++++------- .../lib/active_job/serializers/base_serializer.rb | 32 +++++++++++--------- .../active_job/serializers/duration_serializer.rb | 22 +++++++------- .../active_job/serializers/global_id_serializer.rb | 30 +++++++++---------- .../lib/active_job/serializers/hash_serializer.rb | 34 ++++++++++------------ .../hash_with_indifferent_access_serializer.rb | 26 ++++++++--------- .../active_job/serializers/object_serializer.rb | 12 ++++---- .../serializers/standard_type_serializer.rb | 24 +++++++-------- .../active_job/serializers/symbol_serializer.rb | 18 +++++------- 9 files changed, 103 insertions(+), 115 deletions(-) (limited to 'activejob/lib') diff --git a/activejob/lib/active_job/serializers/array_serializer.rb b/activejob/lib/active_job/serializers/array_serializer.rb index 1b3c3b2ce3..9db4edea99 100644 --- a/activejob/lib/active_job/serializers/array_serializer.rb +++ b/activejob/lib/active_job/serializers/array_serializer.rb @@ -3,24 +3,22 @@ module ActiveJob module Serializers # Provides methods to serialize and deserialize `Array` - class ArraySerializer < BaseSerializer - class << self - alias_method :deserialize?, :serialize? + class ArraySerializer < BaseSerializer # :nodoc: + alias_method :deserialize?, :serialize? - def serialize(array) - array.map { |arg| Serializers.serialize(arg) } - end + def serialize(array) + array.map { |arg| Serializers.serialize(arg) } + end - def deserialize(array) - array.map { |arg| Serializers.deserialize(arg) } - end + def deserialize(array) + array.map { |arg| Serializers.deserialize(arg) } + end - private + private def klass Array end - end end end end diff --git a/activejob/lib/active_job/serializers/base_serializer.rb b/activejob/lib/active_job/serializers/base_serializer.rb index 8b891cca48..2e510781cf 100644 --- a/activejob/lib/active_job/serializers/base_serializer.rb +++ b/activejob/lib/active_job/serializers/base_serializer.rb @@ -3,29 +3,33 @@ module ActiveJob module Serializers class BaseSerializer + include Singleton + class << self - def serialize?(argument) - argument.is_a?(klass) - end + delegate :serialize?, :deserialize?, :serialize, :deserialize, to: :instance + end - def deserialize?(_argument) - raise NotImplementedError - end + def serialize?(argument) + argument.is_a?(klass) + end - def serialize(_argument) - raise NotImplementedError - end + def deserialize?(_argument) + raise NotImplementedError + end - def deserialize(_argument) - raise NotImplementedError - end + def serialize(_argument) + raise NotImplementedError + end - private + def deserialize(_argument) + raise NotImplementedError + end + + protected def klass raise NotImplementedError end - 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 46543cc30d..94b0d0407a 100644 --- a/activejob/lib/active_job/serializers/duration_serializer.rb +++ b/activejob/lib/active_job/serializers/duration_serializer.rb @@ -1,24 +1,22 @@ module ActiveJob module Serializers - class DurationSerializer < ObjectSerializer - class << self - def serialize(duration) - super("value" => duration.value, "parts" => Serializers.serialize(duration.parts)) - end + class DurationSerializer < ObjectSerializer # :nodoc: + def serialize(duration) + super("value" => duration.value, "parts" => Serializers.serialize(duration.parts)) + end - def deserialize(hash) - value = hash["value"] - parts = Serializers.deserialize(hash["parts"]) + def deserialize(hash) + value = hash["value"] + parts = Serializers.deserialize(hash["parts"]) - klass.new(value, parts) - end + klass.new(value, parts) + end - private + private def klass ActiveSupport::Duration end - end end end end diff --git a/activejob/lib/active_job/serializers/global_id_serializer.rb b/activejob/lib/active_job/serializers/global_id_serializer.rb index 84ed33ef99..2d8629ef02 100644 --- a/activejob/lib/active_job/serializers/global_id_serializer.rb +++ b/activejob/lib/active_job/serializers/global_id_serializer.rb @@ -4,29 +4,27 @@ module ActiveJob module Serializers # Provides methods to serialize and deserialize objects which mixes `GlobalID::Identification`, # including `ActiveRecord::Base` models - class GlobalIDSerializer < BaseSerializer - class << self - 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 + 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(hash) + GlobalID::Locator.locate(hash[GLOBALID_KEY]) + end - def deserialize?(argument) - argument.is_a?(Hash) && argument[GLOBALID_KEY] - end + def deserialize?(argument) + argument.is_a?(Hash) && argument[GLOBALID_KEY] + end - private + private def klass GlobalID::Identification end - end end end end diff --git a/activejob/lib/active_job/serializers/hash_serializer.rb b/activejob/lib/active_job/serializers/hash_serializer.rb index 2bbb31946d..e569fe7501 100644 --- a/activejob/lib/active_job/serializers/hash_serializer.rb +++ b/activejob/lib/active_job/serializers/hash_serializer.rb @@ -4,26 +4,25 @@ 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 - class << self - def serialize(hash) - symbol_keys = hash.each_key.grep(Symbol).map(&:to_s) - result = serialize_hash(hash) - result[key] = symbol_keys - result - end + 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?(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 + def deserialize(hash) + result = hash.transform_values { |v| Serializers::deserialize(v) } + symbol_keys = result.delete(key) + transform_symbol_keys(result, symbol_keys) + end - private + private def key SYMBOL_KEYS_KEY @@ -56,7 +55,6 @@ module ActiveJob def klass Hash end - 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 index af3576dd57..3b812ba304 100644 --- a/activejob/lib/active_job/serializers/hash_with_indifferent_access_serializer.rb +++ b/activejob/lib/active_job/serializers/hash_with_indifferent_access_serializer.rb @@ -4,21 +4,20 @@ module ActiveJob module Serializers # Provides methods to serialize and deserialize `ActiveSupport::HashWithIndifferentAccess` # Values will be serialized by known serializers - class HashWithIndifferentAccessSerializer < HashSerializer - class << self - def serialize(hash) - result = serialize_hash(hash) - result[key] = Serializers.serialize(true) - result - end + 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 + def deserialize(hash) + result = hash.transform_values { |v| Serializers.deserialize(v) } + result.delete(key) + result.with_indifferent_access + end - private + private def key WITH_INDIFFERENT_ACCESS_KEY @@ -27,7 +26,6 @@ module ActiveJob def klass ActiveSupport::HashWithIndifferentAccess end - 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 d5ff8c91f1..318eabebdf 100644 --- a/activejob/lib/active_job/serializers/object_serializer.rb +++ b/activejob/lib/active_job/serializers/object_serializer.rb @@ -3,14 +3,12 @@ module ActiveJob module Serializers class ObjectSerializer < BaseSerializer - class << self - def serialize(hash) - { OBJECT_SERIALIZER_KEY => self.name }.merge!(hash) - end + def serialize(hash) + { OBJECT_SERIALIZER_KEY => self.class.name }.merge!(hash) + end - def deserialize?(argument) - argument.is_a?(Hash) && argument[OBJECT_SERIALIZER_KEY] == self.name - end + def deserialize?(argument) + argument.is_a?(Hash) && argument[OBJECT_SERIALIZER_KEY] == self.class.name 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 index efc02adcf0..1db4f3937d 100644 --- a/activejob/lib/active_job/serializers/standard_type_serializer.rb +++ b/activejob/lib/active_job/serializers/standard_type_serializer.rb @@ -4,22 +4,20 @@ module ActiveJob module Serializers # Provides methods to serialize and deserialize standard types # (`NilClass`, `String`, `Integer`, `Fixnum`, `Bignum`, `Float`, `BigDecimal`, `TrueClass`, `FalseClass`) - class StandardTypeSerializer < BaseSerializer - class << self - def serialize?(argument) - Arguments::TYPE_WHITELIST.include? argument.class - end + class StandardTypeSerializer < BaseSerializer # :nodoc: + def serialize?(argument) + Arguments::TYPE_WHITELIST.include? argument.class + end - def serialize(argument) - argument - end + def serialize(argument) + argument + end - alias_method :deserialize?, :serialize? + alias_method :deserialize?, :serialize? - def deserialize(argument) - object = GlobalID::Locator.locate(argument) if argument.is_a? String - object || argument - end + def deserialize(argument) + object = GlobalID::Locator.locate(argument) if argument.is_a? String + object || argument end end end diff --git a/activejob/lib/active_job/serializers/symbol_serializer.rb b/activejob/lib/active_job/serializers/symbol_serializer.rb index ec27f6828a..c8900de9d6 100644 --- a/activejob/lib/active_job/serializers/symbol_serializer.rb +++ b/activejob/lib/active_job/serializers/symbol_serializer.rb @@ -1,21 +1,19 @@ module ActiveJob module Serializers - class SymbolSerializer < ObjectSerializer - class << self - def serialize(argument) - super("value" => argument.to_s) - end + class SymbolSerializer < ObjectSerializer # :nodoc: + def serialize(argument) + super("value" => argument.to_s) + end - def deserialize(argument) - argument["value"].to_sym - end + def deserialize(argument) + argument["value"].to_sym + end - private + private def klass Symbol end - end end end end -- cgit v1.2.3 From d9a5c7011f62dd771a2fa430090e068b1f9785f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 9 Feb 2018 16:50:57 -0500 Subject: Add serializers for Time, Date and DateTime --- activejob/lib/active_job/serializers.rb | 8 +++++++- .../lib/active_job/serializers/date_serializer.rb | 21 +++++++++++++++++++++ .../active_job/serializers/date_time_serializer.rb | 21 +++++++++++++++++++++ .../active_job/serializers/duration_serializer.rb | 2 ++ .../lib/active_job/serializers/symbol_serializer.rb | 2 ++ .../lib/active_job/serializers/time_serializer.rb | 21 +++++++++++++++++++++ 6 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 activejob/lib/active_job/serializers/date_serializer.rb create mode 100644 activejob/lib/active_job/serializers/date_time_serializer.rb create mode 100644 activejob/lib/active_job/serializers/time_serializer.rb (limited to 'activejob/lib') diff --git a/activejob/lib/active_job/serializers.rb b/activejob/lib/active_job/serializers.rb index 9e3fcda28d..dfd654175d 100644 --- a/activejob/lib/active_job/serializers.rb +++ b/activejob/lib/active_job/serializers.rb @@ -36,6 +36,9 @@ module ActiveJob autoload :StandardTypeSerializer autoload :SymbolSerializer autoload :DurationSerializer + autoload :DateSerializer + autoload :TimeSerializer + autoload :DateTimeSerializer mattr_accessor :_additional_serializers self._additional_serializers = Set.new @@ -98,6 +101,9 @@ module ActiveJob HashSerializer, ArraySerializer, SymbolSerializer, - DurationSerializer + DurationSerializer, + DateTimeSerializer, + DateSerializer, + 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 index 94b0d0407a..a3c4c5d1c2 100644 --- a/activejob/lib/active_job/serializers/duration_serializer.rb +++ b/activejob/lib/active_job/serializers/duration_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveJob module Serializers class DurationSerializer < ObjectSerializer # :nodoc: diff --git a/activejob/lib/active_job/serializers/symbol_serializer.rb b/activejob/lib/active_job/serializers/symbol_serializer.rb index c8900de9d6..7e1f9553a2 100644 --- a/activejob/lib/active_job/serializers/symbol_serializer.rb +++ b/activejob/lib/active_job/serializers/symbol_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveJob module Serializers class SymbolSerializer < ObjectSerializer # :nodoc: 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 -- cgit v1.2.3 From 2fe467091b3743627d52a3e2ae357f0b5fd6d157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 9 Feb 2018 17:26:35 -0500 Subject: No need to require a autoloaded constant --- activejob/lib/active_job/base.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'activejob/lib') diff --git a/activejob/lib/active_job/base.rb b/activejob/lib/active_job/base.rb index 8275776820..6194f89956 100644 --- a/activejob/lib/active_job/base.rb +++ b/activejob/lib/active_job/base.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "active_job/core" -require "active_job/serializers" require "active_job/queue_adapter" require "active_job/queue_name" require "active_job/queue_priority" -- cgit v1.2.3 From a5f7357a3dff2617ba13a274feb8d8ac2492f26a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 9 Feb 2018 17:27:01 -0500 Subject: Add configuration to set custom serializers --- activejob/lib/active_job/railtie.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'activejob/lib') diff --git a/activejob/lib/active_job/railtie.rb b/activejob/lib/active_job/railtie.rb index 7b0742a6d2..427ad1e3af 100644 --- a/activejob/lib/active_job/railtie.rb +++ b/activejob/lib/active_job/railtie.rb @@ -7,11 +7,17 @@ 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| + custom_serializers = app.config.active_job.delete(:custom_serializers) + ActiveJob::Serializers.add_serializers custom_serializers + end + initializer "active_job.set_configs" do |app| options = app.config.active_job options.queue_adapter ||= :async -- cgit v1.2.3 From 71721dc1c9b769d3c06317122dc88cad4a346580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 9 Feb 2018 17:27:39 -0500 Subject: Improve documentation on custom serializers --- .../lib/active_job/serializers/base_serializer.rb | 6 ++++++ .../lib/active_job/serializers/object_serializer.rb | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) (limited to 'activejob/lib') diff --git a/activejob/lib/active_job/serializers/base_serializer.rb b/activejob/lib/active_job/serializers/base_serializer.rb index 2e510781cf..155eeb29c3 100644 --- a/activejob/lib/active_job/serializers/base_serializer.rb +++ b/activejob/lib/active_job/serializers/base_serializer.rb @@ -2,6 +2,7 @@ module ActiveJob module Serializers + # Implement the basic interface for Active Job arguments serializers. class BaseSerializer include Singleton @@ -9,24 +10,29 @@ module ActiveJob 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 diff --git a/activejob/lib/active_job/serializers/object_serializer.rb b/activejob/lib/active_job/serializers/object_serializer.rb index 318eabebdf..940b6ff95d 100644 --- a/activejob/lib/active_job/serializers/object_serializer.rb +++ b/activejob/lib/active_job/serializers/object_serializer.rb @@ -2,6 +2,25 @@ module ActiveJob module Serializers + # Base class for serializing and deserializing custom times. + # + # Example + # + # class MoneySerializer < ActiveJob::Serializers::ObjectSerializer + # def serialize(money) + # super("cents" => money.cents, "currency" => money.currency) + # end + # + # def deserialize(hash) + # Money.new(hash["cents"], hash["currency"]) + # end + # + # private + # + # def klass + # Money + # end + # end class ObjectSerializer < BaseSerializer def serialize(hash) { OBJECT_SERIALIZER_KEY => self.class.name }.merge!(hash) -- cgit v1.2.3 From 69645cba727dfa1c18c666d2a2f1c0dedffde938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Mon, 12 Feb 2018 14:16:41 -0500 Subject: Simplify the implementation of custom argument serializers We can speed up things for the supported types by keeping the code in the way it was. We can also avoid to loop trough all serializers in the deserialization by trying to access the class already in the Hash. We could also speed up the custom serialization if we define the class that is going to be serialized when registering the serializers, but that will remove the possibility of defining a serialzer for a superclass and have the subclass serialized using it. --- activejob/lib/active_job/arguments.rb | 141 ++++++++++++++++++++- activejob/lib/active_job/serializers.rb | 61 +-------- .../lib/active_job/serializers/array_serializer.rb | 24 ---- .../lib/active_job/serializers/base_serializer.rb | 41 ------ .../active_job/serializers/duration_serializer.rb | 4 +- .../active_job/serializers/global_id_serializer.rb | 30 ----- .../lib/active_job/serializers/hash_serializer.rb | 60 --------- .../hash_with_indifferent_access_serializer.rb | 31 ----- .../active_job/serializers/object_serializer.rb | 28 +++- .../serializers/standard_type_serializer.rb | 24 ---- 10 files changed, 172 insertions(+), 272 deletions(-) delete mode 100644 activejob/lib/active_job/serializers/array_serializer.rb delete mode 100644 activejob/lib/active_job/serializers/base_serializer.rb delete mode 100644 activejob/lib/active_job/serializers/global_id_serializer.rb delete mode 100644 activejob/lib/active_job/serializers/hash_serializer.rb delete mode 100644 activejob/lib/active_job/serializers/hash_with_indifferent_access_serializer.rb delete mode 100644 activejob/lib/active_job/serializers/standard_type_serializer.rb (limited to 'activejob/lib') 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 ActiveJob::Serializers 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 -- cgit v1.2.3