Fix ActiveJob arguments serialization to correctly serialize String subclasses having custom serializers

Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
This commit is contained in:
fatkodima 2023-11-18 01:43:35 +02:00
parent e514c586f1
commit 14578eaa4f
2 changed files with 47 additions and 11 deletions

View File

@ -46,8 +46,6 @@ module ActiveJob
end end
private private
# :nodoc:
PERMITTED_TYPES = [ NilClass, String, Integer, Float, TrueClass, FalseClass ]
# :nodoc: # :nodoc:
GLOBALID_KEY = "_aj_globalid" GLOBALID_KEY = "_aj_globalid"
# :nodoc: # :nodoc:
@ -67,13 +65,19 @@ module ActiveJob
OBJECT_SERIALIZER_KEY, OBJECT_SERIALIZER_KEY.to_sym, OBJECT_SERIALIZER_KEY, OBJECT_SERIALIZER_KEY.to_sym,
WITH_INDIFFERENT_ACCESS_KEY, WITH_INDIFFERENT_ACCESS_KEY.to_sym, WITH_INDIFFERENT_ACCESS_KEY, WITH_INDIFFERENT_ACCESS_KEY.to_sym,
] ]
private_constant :PERMITTED_TYPES, :RESERVED_KEYS, :GLOBALID_KEY, private_constant :RESERVED_KEYS, :GLOBALID_KEY,
:SYMBOL_KEYS_KEY, :RUBY2_KEYWORDS_KEY, :WITH_INDIFFERENT_ACCESS_KEY :SYMBOL_KEYS_KEY, :RUBY2_KEYWORDS_KEY, :WITH_INDIFFERENT_ACCESS_KEY
def serialize_argument(argument) def serialize_argument(argument)
case argument case argument
when *PERMITTED_TYPES when nil, true, false, Integer, Float # Types that can hardly be subclassed
argument argument
when String
if argument.class == String
argument
else
Serializers.serialize(argument)
end
when GlobalID::Identification when GlobalID::Identification
convert_to_global_id_hash(argument) convert_to_global_id_hash(argument)
when Array when Array
@ -90,10 +94,10 @@ module ActiveJob
result = serialize_hash(argument) result = serialize_hash(argument)
result[aj_hash_key] = symbol_keys result[aj_hash_key] = symbol_keys
result result
when -> (arg) { arg.respond_to?(:permitted?) && arg.respond_to?(:to_h) }
serialize_indifferent_hash(argument.to_h)
else else
if BigDecimal === argument && !ActiveJob.use_big_decimal_serializer if argument.respond_to?(:permitted?) && argument.respond_to?(:to_h)
serialize_indifferent_hash(argument.to_h)
elsif BigDecimal === argument && !ActiveJob.use_big_decimal_serializer
ActiveJob.deprecator.warn(<<~MSG) ActiveJob.deprecator.warn(<<~MSG)
Primitive serialization of BigDecimal job arguments is deprecated as it may serialize via .to_s using certain queue adapters. Primitive serialization of BigDecimal job arguments is deprecated as it may serialize via .to_s using certain queue adapters.
Enable config.active_job.use_big_decimal_serializer to use BigDecimalSerializer instead, which will be mandatory in Rails 7.2. Enable config.active_job.use_big_decimal_serializer to use BigDecimalSerializer instead, which will be mandatory in Rails 7.2.
@ -101,16 +105,16 @@ module ActiveJob
Note that if your application has multiple replicas, you should only enable this setting after successfully deploying your app to Rails 7.1 first. Note that if your application has multiple replicas, you should only enable this setting after successfully deploying your app to Rails 7.1 first.
This will ensure that during your deployment all replicas are capable of deserializing arguments serialized with BigDecimalSerializer. This will ensure that during your deployment all replicas are capable of deserializing arguments serialized with BigDecimalSerializer.
MSG MSG
return argument argument
end else
Serializers.serialize(argument) Serializers.serialize(argument)
end end
end end
end
def deserialize_argument(argument) def deserialize_argument(argument)
case argument case argument
when *PERMITTED_TYPES when nil, true, false, String, Integer, Float
argument argument
when BigDecimal # BigDecimal may have been legacy serialized; Remove in 7.2 when BigDecimal # BigDecimal may have been legacy serialized; Remove in 7.2
argument argument

View File

@ -1,5 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "json"
require "bigdecimal" require "bigdecimal"
require "helper" require "helper"
require "active_job/arguments" require "active_job/arguments"
@ -23,6 +24,24 @@ class ArgumentSerializationTest < ActiveSupport::TestCase
end end
end end
class MyString < String
end
class MyStringSerializer < ActiveJob::Serializers::ObjectSerializer
def serialize(argument)
super({ "value" => argument.to_s })
end
def deserialize(hash)
MyString.new(hash["value"])
end
private
def klass
MyString
end
end
setup do setup do
@person = Person.find("5") @person = Person.find("5")
end end
@ -124,6 +143,19 @@ class ArgumentSerializationTest < ActiveSupport::TestCase
assert_arguments_unchanged MyClassWithPermitted assert_arguments_unchanged MyClassWithPermitted
end end
test "serialize a String subclass object" do
original_serializers = ActiveJob::Serializers.serializers
ActiveJob::Serializers.add_serializers(MyStringSerializer)
my_string = MyString.new("foo")
serialized = ActiveJob::Arguments.serialize([my_string])
deserialized = ActiveJob::Arguments.deserialize(JSON.load(JSON.dump(serialized))).first
assert_instance_of MyString, deserialized
assert_equal my_string, deserialized
ensure
ActiveJob::Serializers._additional_serializers = original_serializers
end
test "serialize a hash" do test "serialize a hash" do
symbol_key = { a: 1 } symbol_key = { a: 1 }
string_key = { "a" => 1 } string_key = { "a" => 1 }