aglushkov/serega

View on GitHub
lib/serega/object_serializer.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
# frozen_string_literal: true

class Serega
  #
  # Low-level class that is used by more high-level SeregaSerializer
  # to construct serialized to hash response
  #
  class SeregaObjectSerializer
    #
    # SeregaObjectSerializer instance methods
    #
    module InstanceMethods
      attr_reader :context, :plan, :many, :opts

      # @param plan [SeregaPlan] Serialization plan
      # @param context [Hash] Serialization context
      # @param many [Boolean] is object is enumerable
      # @param opts [Hash] Any custom options
      #
      # @return [SeregaObjectSerializer] New SeregaObjectSerializer
      def initialize(context:, plan:, many: nil, **opts)
        @context = context
        @plan = plan
        @many = many
        @opts = opts
      end

      # Serializes object(s)
      #
      # @param object [Object] Serialized object
      #
      # @return [Hash, Array<Hash>, nil] Serialized object(s)
      def serialize(object)
        return if object.nil?

        array?(object, many) ? serialize_array(object) : serialize_object(object)
      end

      private

      def serialize_array(objects)
        objects.map { |object| serialize_object(object) }
      end

      # Patched in:
      # - plugin :presenter (makes presenter_object and serializes it)
      def serialize_object(object)
        plan.points.each_with_object({}) do |point, container|
          serialize_point(object, point, container)
        rescue SeregaError
          raise
        rescue => error
          reraise_with_serialized_attribute_details(error, point)
        end
      end

      # Patched in:
      # - plugin :if (conditionally skips serializing this point)
      def serialize_point(object, point, container)
        attach_value(object, point, container)
      end

      # Patched in:
      # - plugin :batch (remembers key for batch loading values instead of attaching)
      def attach_value(object, point, container)
        value = point.value(object, context)
        final_value = final_value(value, point)
        attach_final_value(final_value, point, container)
      end

      # Patched in:
      # - plugin :if (conditionally skips attaching)
      # - plugin :batch :if extension (removes prepared key)
      def attach_final_value(final_value, point, container)
        container[point.name] = final_value
      end

      def final_value(value, point)
        point.child_plan ? relation_value(value, point) : value
      end

      def relation_value(value, point)
        child_serializer(point).serialize(value)
      end

      def child_serializer(point)
        point.child_object_serializer.new(
          context: context,
          plan: point.child_plan,
          many: point.many,
          **opts
        )
      end

      def array?(object, many)
        many.nil? ? object.is_a?(Enumerable) : many
      end

      def reraise_with_serialized_attribute_details(error, point)
        raise error.exception(<<~MESSAGE.strip)
          #{error.message}
          (when serializing '#{point.name}' attribute in #{self.class.serializer_class})
        MESSAGE
      end
    end

    extend Serega::SeregaHelpers::SerializerClassHelper
    include InstanceMethods
  end
end