dougyouch/schema

View on GitHub
lib/schema/arrays.rb

Summary

Maintainability
C
7 hrs
Test Coverage
A
100%
# frozen_string_literal: true

module Schema
  # Schema::Arrays maps the array to a schema model
  module Arrays
    def self.included(base)
      base.extend ClassMethods
    end

    # adds from_array method to the class
    module ClassMethods
      def from_array(array, mapped_headers)
        new.update_attributes_with_array(array, mapped_headers)
      end

      def to_empty_array
        data = []
        schema.each do |_, field_options|
          next if field_options[:alias_of]

          data <<
            case field_options[:type]
            when :has_one
              const_get(field_options[:class_name]).to_empty_array
            when :has_many
              field_options[:size].times.map { const_get(field_options[:class_name]).to_empty_array }
            else
              nil
            end
        end
        data
      end

      def to_headers(prefix = nil)
        headers = []
        schema.each do |_, field_options|
          next if field_options[:alias_of]

          headers <<
            case field_options[:type]
            when :has_one
              const_get(field_options[:class_name]).to_headers(prefix.to_s + field_options[:key] + '.')
            when :has_many
              field_options[:size].times.map do |i|
                const_get(field_options[:class_name]).to_headers(prefix.to_s + field_options[:key] + "[#{i +1}].")
              end
            else
              prefix.to_s + field_options[:key]
            end
        end
        headers.flatten
      end
    end

    def to_a
      data = []
      self.class.schema.each do |_, field_options|
        next if field_options[:alias_of]

        value = public_send(field_options[:getter])
        data <<
          case field_options[:type]
          when :has_one
            value ? value.to_a : self.class.const_get(field_options[:class_name]).to_empty_array
          when :has_many
            values = value || []
            field_options[:size].times.map do |idx|
              value = values[idx]
              value ? value.to_a : self.class.const_get(field_options[:class_name]).to_empty_array
            end
          else
            value
          end
      end
      data
    end

    def update_attributes_with_array(array, mapped_headers, offset = nil)
      self.class.schema.each do |_, field_options|
        next unless (mapped_field = mapped_headers[field_options[:name]])

        if offset
          next unless mapped_field[:indexes]
          next unless (index = mapped_field[:indexes][offset])
        else
          next unless (index = mapped_field[:index])
        end

        public_send(
          field_options[:setter],
          array[index]
        )
      end

      update_nested_schemas_from_array(array, mapped_headers, offset)

      self
    end

    def update_nested_schemas_from_array(array, mapped_headers, current_offset = nil)
      update_nested_has_one_associations_from_array(array, mapped_headers, current_offset)
      update_nested_has_many_associations_from_array(array, mapped_headers)
    end

    def update_nested_has_one_associations_from_array(array, mapped_headers, current_offset = nil)
      self.class.schema.each do |_, field_options|
        next unless field_options[:type] == :has_one
        next unless (mapped_model = mapped_headers[field_options[:name]])

        instance_variable_set(
          field_options[:instance_variable],
          create_schema_with_array(field_options, array, mapped_model, current_offset)
        )
      end
    end

    def update_nested_has_many_associations_from_array(array, mapped_headers)
      self.class.schema.each do |_, field_options|
        next unless field_options[:type] == :has_many
        next unless (mapped_model = mapped_headers[field_options[:name]])

        size = largest_number_of_indexes_from_map(mapped_model)

        instance_variable_set(
          field_options[:instance_variable],
          size.times.map do |offset|
            create_schema_with_array(field_options, array, mapped_model, offset)
          end
        )
      end
    end

    def create_schema_with_array(field_options, array, mapped_model, offset)
      self.class.const_get(field_options[:class_name]).new.update_attributes_with_array(array, mapped_model, offset)
    end

    def largest_number_of_indexes_from_map(mapped_model)
      size = 0
      mapped_model.each do |_, info|
        if info[:indexes]
          size = info[:indexes].size if info[:indexes] && info[:indexes].size > size
        else
          new_size = largest_number_of_indexes_from_map(info)
          size = new_size if new_size > size
        end
      end
      size
    end
  end
end