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/serializers.rb | 109 ++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 activejob/lib/active_job/serializers.rb (limited to 'activejob/lib/active_job/serializers.rb') 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 -- 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 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) (limited to 'activejob/lib/active_job/serializers.rb') 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 -- 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 ++++++++++++++------------------- 1 file changed, 27 insertions(+), 35 deletions(-) (limited to 'activejob/lib/active_job/serializers.rb') 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 -- 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 ++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 15 deletions(-) (limited to 'activejob/lib/active_job/serializers.rb') 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, -- 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 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'activejob/lib/active_job/serializers.rb') 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 -- 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 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'activejob/lib/active_job/serializers.rb') 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 -- 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/serializers.rb | 61 ++++----------------------------- 1 file changed, 7 insertions(+), 54 deletions(-) (limited to 'activejob/lib/active_job/serializers.rb') 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, -- cgit v1.2.3