rom-rb/rom-repository

View on GitHub
lib/rom/repository/struct_builder.rb

Summary

Maintainability
A
0 mins
Test Coverage
require 'dry/core/inflector'
require 'dry/core/cache'
require 'dry/core/class_builder'

require 'rom/struct'
require 'rom/open_struct'
require 'rom/schema/attribute'

module ROM
  class Repository
    # @api private
    class StructBuilder
      extend Dry::Core::Cache

      def call(*args)
        fetch_or_store(*args) do
          name, header = args
          attributes = visit(header).compact

          if attributes.empty?
            ROM::OpenStruct
          else
            build_class(name, ROM::Struct) do |klass|
              attributes.each do |(name, type)|
                klass.attribute(name, type)
              end
            end
          end
        end
      end
      alias_method :[], :call

      attr_reader :namespace

      def initialize(namespace = nil)
        @namespace = namespace || ROM::Struct
      end

      private

      def visit(ast)
        name, node = ast
        __send__("visit_#{name}", node)
      end

      def visit_header(node)
        node.map(&method(:visit))
      end

      def visit_relation(node)
        relation_name, meta, header = node
        name = meta[:combine_name] || relation_name.relation

        model = meta.fetch(:model) { call(name, header) }

        member =
          if model < Dry::Struct
            model
          else
            Dry::Types::Definition.new(model).constructor(&model.method(:new))
          end

        if meta[:combine_type] == :many
          [name, Types::Array.member(member)]
        else
          [name, member.optional]
        end
      end

      def visit_attribute(attr)
        [attr.aliased? && !attr.wrapped? ? attr.alias : attr.name, attr.to_read_type]
      end

      def build_class(name, parent, &block)
        Dry::Core::ClassBuilder.new(name: class_name(name), parent: parent, namespace: namespace).call(&block)
      end

      def class_name(name)
        Dry::Core::Inflector.classify(Dry::Core::Inflector.singularize(name))
      end
    end
  end
end