aglushkov/serega

View on GitHub
lib/serega/plugins/batch/lib/loader.rb

Summary

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

class Serega
  module SeregaPlugins
    module Batch
      #
      # Encapsulates point and according object_serializer
      #   so we can put batch loaded values to this serializer response
      #
      class SeregaBatchLoader
        #
        # Batch Loader instance methods
        #
        module InstanceMethods
          # @return [Serega::SeregaPlanPoint]
          attr_reader :point

          # @return [Serega::SeregaObjectSerializer]
          attr_reader :object_serializer

          #
          # Initializes new SeregaBatchLoader
          #
          # @param object_serializer [Serega::SeregaObjectSerializer]
          # @param point [Serega::SeregaPlanPoint]
          #
          # @return [Serega::SeregaPlugins::Batch::SeregaBatchLoader]
          #
          def initialize(object_serializer, point)
            @object_serializer = object_serializer
            @point = point
          end

          #
          # Remembers key and hash container where value for this key must be inserted
          #
          # @param key [Object] key that identifies batch loaded objects
          # @param container [Hash] container where batch loaded objects must be attached
          #
          # @return [void]
          #
          def remember(key, container)
            (keys[key] ||= []) << container
          end

          #
          # Loads this batch and assigns values to remembered containers
          #
          # @return [void]
          #
          def load
            keys_values = keys_values()

            each_key do |key, container|
              value = keys_values.fetch(key) { point.batch[:default] }
              final_value = object_serializer.__send__(:final_value, value, point)
              object_serializer.__send__(:attach_final_value, final_value, point, container)
            end
          end

          private

          def keys
            @keys ||= {}
          end

          def each_key
            keys.each do |key, containers|
              containers.each do |container|
                yield(key, container)
              end
            end
          end

          # Patched in:
          # - plugin batch (extension :activerecord_preloads - preloads data to found values)
          # - plugin batch (extension :formatters - formats values)
          def keys_values
            ids = keys.keys

            keys_values = load_keys_values(ids)
            validate(keys_values)

            keys_values
          end

          def load_keys_values(ids)
            point.batch[:loader].call(ids, object_serializer.context, point)
          rescue => error
            raise reraise_with_serialized_attribute_details(error, point)
          end

          def validate(keys_values)
            return if keys_values.is_a?(Hash)

            attribute_name = "#{point.class.serializer_class}.#{point.name}"
            raise SeregaError, "Batch loader for `#{attribute_name}` must return Hash, but #{keys_values.inspect} was returned"
          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

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