Repository: rails/rails
File: activejob/lib/active_job/serializers.rb (Ruby)

Code before:
  # 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 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 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

Review comment (bug):
I thought about adding an optimization like this but I was concerned it could interfere with a serializer who's `serialize?` method returns `true` or `false` based on the properties of the given object, for example:

```ruby
class CustomClass
  attr_reader :foo

  def initialize(foo)
    @foo = foo
  end
end

class CustomClassSerializer < ActiveJob::Serializers::ObjectSerializer
  def serialize?(argument)
    !!argument.foo
  end
end
```

Memoizing the mapping from class -> serializer would use `CustomClassSerializer` for instances of `CustomClass` that have a nil `@foo` and ones that don't.

I doubt this is a common use-case, but it is a change in behavior that could be subtle and difficult to debug for Rails devs.

Code after:
  # 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
    autoload :ActionControllerParametersSerializer

    @serializers = Set.new
    @serializers_index = {}

    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_index[argument.class] || serializers.find { |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.
      attr_reader :serializers

      def serializers=(serializers)
        @serializers = serializers
        index_serializers
      end