1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/activejob/lib/active_job/serializers.rb
Sam Bostock bc1f323338
Add ActiveJob::Serializers::BigDecimalSerializer
Previously, BigDecimal was listed as not needing a serializer.  However,
when used with an adapter storing the job arguments as JSON, it would get
serialized as a simple String, resulting in deserialization also producing
a String (instead of a BigDecimal).

By using a serializer, we ensure the round trip is safe.

During upgrade deployments of applications with multiple replicas making use of
BigDecimal job arguments with a queue adapter serializing to JSON, there exists
a possible race condition, whereby a "new" replica enqueues a job with an
argument serialized using `BigDecimalSerializer`, and an "old" replica fails to
deserialize it (as it does not have `BigDecimalSerializer`).

Therefore, to ensure safe upgrades, serialization will not use
`BigDecimalSerializer` until `config.active_job.use_big_decimal_serializer` is
enabled, which can be done safely after successful deployment of Rails 7.1.

This option will be removed in Rails 7.2, when it will become the default.
2022-07-19 15:31:41 -04:00

70 lines
2.3 KiB
Ruby

# frozen_string_literal: true
require "set"
module ActiveJob
# The <tt>ActiveJob::Serializers</tt> module is used to store a list of known serializers
# and to add new ones. It also has helpers to serialize/deserialize objects.
module Serializers # :nodoc:
extend ActiveSupport::Autoload
autoload :ObjectSerializer
autoload :TimeObjectSerializer
autoload :SymbolSerializer
autoload :DurationSerializer
autoload :DateTimeSerializer
autoload :DateSerializer
autoload :TimeWithZoneSerializer
autoload :TimeSerializer
autoload :ModuleSerializer
autoload :RangeSerializer
autoload :BigDecimalSerializer
mattr_accessor :_additional_serializers
self._additional_serializers = Set.new
class << self
# Returns serialized representative of the passed object.
# Will look up through all known serializers.
# Raises <tt>ActiveJob::SerializationError</tt> if it can't find a proper serializer.
def serialize(argument)
serializer = serializers.detect { |s| s.serialize?(argument) }
raise SerializationError.new("Unsupported argument type: #{argument.class.name}") unless serializer
serializer.serialize(argument)
end
# Returns deserialized object.
# Will look up through all known serializers.
# If no serializer found will raise <tt>ArgumentError</tt>.
def deserialize(argument)
serializer_name = argument[Arguments::OBJECT_SERIALIZER_KEY]
raise ArgumentError, "Serializer name is not present in the argument: #{argument.inspect}" unless serializer_name
serializer = serializer_name.safe_constantize
raise ArgumentError, "Serializer #{serializer_name} is not known" unless serializer
serializer.deserialize(argument)
end
# Returns list of known serializers.
def serializers
self._additional_serializers
end
# Adds new serializers to a list of known serializers.
def add_serializers(*new_serializers)
self._additional_serializers += new_serializers.flatten
end
end
add_serializers SymbolSerializer,
DurationSerializer,
DateTimeSerializer,
DateSerializer,
TimeWithZoneSerializer,
TimeSerializer,
ModuleSerializer,
RangeSerializer,
BigDecimalSerializer
end
end