class Eco::API::Common::People::PersonEntryAttributeMapper

@attr_reader direct_attrs [Array<String>] only those internal attributes present in the person entry that do not have an internal/external name mapping.

def account_attrs(data = nil)

Returns:
  • (Array) - account attributes that are present in the person entry.
def account_attrs(data = nil)
  return @account_attrs unless data || !@account_attrs
  @person_parser.target_attrs_account(internal_attrs(data)).tap do |account_attrs|
    @account_attrs ||= account_attrs
  end
end

def aliased_attrs

Returns:
  • (Array) - only those internal attributes
def aliased_attrs
  return @aliased_attrs if @aliased_attrs
  if parsing?
    init_attr_trackers
  else
    @aliased_attrs = @attr_map.list(:internal)
  end
  @aliased_attrs
end

def all_model_attrs(data = nil)

Returns:
  • (Array) - all the attrs that are present in the person entry.
def all_model_attrs(data = nil)
  core_attrs(data) | account_attrs(data) | details_attrs(data)
end

def attributes(value)

def attributes(value)
  case value
  when CSV::Row
    value&.headers
  when Hash
    value&.keys
  when PersonEntry
    @person_parser.target_attrs_core
  else
    []
  end
end

def cached_warning(*args)

def cached_warning(*args)
  unless exists = !!@@cached_warnings.dig(*args)
    args.reduce(@@cached_warnings) do |cache, level|
      cache[level] = {} unless cache.key?(level)
      cache[level]
    end
  end
  exists
end

def core_attrs(data = nil)

Returns:
  • (Array) - core attributes that are present in the person entry.
def core_attrs(data = nil)
  return @core_attrs unless data || !@core_attrs
  @person_parser.target_attrs_core(internal_attrs(data)).tap do |core_attrs|
    @core_attrs ||= core_attrs
  end
end

def debug(var, msg)

def debug(var, msg)
  return unless DEBUG
  puts "\n  • #{msg}:"
  pp var
end

def details_attrs(data = nil)

Returns:
  • (Array) - schema details attributes that are present in the person entry.
def details_attrs(data = nil)
  return @details_attrs unless data || !@details_attrs
  @person_parser.target_attrs_details(internal_attrs(data)).tap do |details_attrs|
    @details_attrs ||= details_attrs
  end
end

def external_attr?(attr)

Whether the maps include this `attr` as mapped from to some other or itself
def external_attr?(attr)
  return false unless @attr_map
  @attr_map.external?(attr)
end

def fatal(msg)

def fatal(msg)
  logger.fatal(msg)
  raise msg
end

def init_attr_trackers # rubocop:disable Metrics/AbcSize, Metrics/MethodLength

rubocop:disable Metrics/AbcSize, Metrics/MethodLength
when parsing:
def init_attr_trackers # rubocop:disable Metrics/AbcSize, Metrics/MethodLength

  # (def) all internal attributes we can expect

  def_all_attrs     = @person_parser.all_model_attrs
  # (def) internal attrs with no aliasing nor parser definition (expected to be direct)

  def_unlinked      = @person_parser.undefined_model_attrs.reject do |attr|
    to_external(attr)
  end
  debug(
    def_unlinked,
    "(def_unlinked) expected to be direct (not parser defined nor aliased)"
  )
  # (data) data attributes (actual attributes of the entry)

  data_attrs              = attributes(@external_entry)
  # (data) attributes of the data that COULD come directly as internal attribute names

  # => WARNING: this includes ext direct attrs that may be aliased to other int attrs

  data_direct_attrs_raw   = data_attrs & def_all_attrs
  # (data) direct (int) ext data attrs mapped

  data_direct_mapped      = data_direct_attrs_raw.select { |attr| external_attr?(attr) }
  # (data) direct (int) ext data attrs mapped to themselves

  data_direct_self_mapped = data_direct_attrs_raw.select { |attr| self_mapped_attr?(attr) }
  # (data) direct (int) ext data attrs mapped only to another attr

  data_direct_renamed     = data_direct_mapped - data_direct_self_mapped
  # (data) attributes of the data that come directly as internal attribute names

  data_direct_attrs       = data_direct_attrs_raw - data_direct_renamed
  debug(
    data_direct_attrs,
    "(data_direct_attrs) attributes of the data that come directly as internal attribute names (data_direct_attrs_raw - data_direct_renamed)" # rubocop:disable Layout/LineLength

  )
  # (def) configured as alised (int <-> ext attrs) + accept/include int attrs as ext attrs

  def_int_aliased_raw = def_all_attrs.select  { |attr| to_external(attr) }
  # (def) aliasable int attrs of the input data (excludes int attrs direct as ext attrs that got renamed)

  def_int_aliased     = def_int_aliased_raw - data_direct_renamed
  # (def) ext attrs of the data's aliasable int attrs (def_int_aliased)

  def_ext_alias       = def_int_aliased.map   { |attr| to_external(attr) }
  # (def) those ext attrs that map to multiple int attrs

  # def_ext_multi_alias = def_ext_alias.detect  { |attr| def_ext_alias.count(attr) > 1 }

  # (def) those ext attrs that are direct, mapt to themselves and some other

  # (data) virtual attrs (external alias of non native internal attr in data):

  data_vi_ext_alias = data_attrs.select do |attr|
    !def_ext_alias.include?(attr) && @attr_map&.external?(attr)
  end
  # (data) virtual internal attrs (the internal names of those virtual attrs)

  data_vi_int_aliased = data_vi_ext_alias.map do |attr|
    # to_internal(attr) can't be used here, becauase virtual fields would get filtered out,

    # as they are not recognized by @parser.all_model_attrs.include?(attr)

    @attr_map.to_internal(attr)
  end.compact
  # (data) int attrs that come aliased in the current data

  # => modify aliased based on those that came directly as internal attrs in the entry

  data_def_int_aliased = (def_int_aliased - data_direct_attrs) | data_direct_self_mapped
  data_def_ext_alias   = data_def_int_aliased.map { |attr| to_external(attr) }
  # (data) ext attrs of the data that come aliased

  data_ext_alias       = data_def_ext_alias & data_attrs
  # (data) all internal attributes that come aliased, with given the entry

  aliased_attrs        = data_def_int_aliased | data_vi_int_aliased
  # render in the order that defines the model

  @aliased_attrs       = (def_all_attrs & aliased_attrs) | (aliased_attrs - def_all_attrs)
  debug(
    data_vi_int_aliased,
    "(data_vi_int_aliased) virtual internal attrs (internal names of those virtual attrs)"
  )
  debug(
    def_int_aliased,
    "(def_int_aliased) aliasable int attrs of the input data (excludes int attrs direct as ext attrs that got renamed)" # rubocop:disable Layout/LineLength

  )
  debug(
    def_ext_alias,
    "(def_ext_alias) ext attrs of the data's aliasable int attrs (def_int_aliased)"
  )
  debug(
    data_def_int_aliased,
    "(data_def_int_aliased) int attrs that come aliased in the current data ((def_int_aliased - data_direct_attrs) | data_direct_self_mapped)" # rubocop:disable Layout/LineLength

  )
  debug(
    data_ext_alias,
    "(data_ext_alias) ext attrs of the data that come aliased (data_def_ext_alias & data_attrs)"
  )
  debug(
    aliased_attrs,
    "(aliased_attrs) all internal attributes that come aliased, with given the entry (data_def_int_aliased | data_vi_int_aliased)" # rubocop:disable Layout/LineLength

  )
  # (data) all those ext attrs present that will require aliasing

  #data_ext_alias_all = data_def_ext_alias   + data_vi_ext_alias

  data_ext_alias_all = data_ext_alias | data_vi_ext_alias
  debug(
    data_ext_alias_all,
    "(data_ext_alias_all) all those ext attrs present that will require aliasing (data_ext_alias | data_vi_ext_alias)" # rubocop:disable Layout/LineLength

  )
  # those that are direct external to internal:

  data_ext_direct      = data_attrs - data_ext_alias_all
  # (data) attributes that do not require aliasing

  # to avoid collisions between internal names:

  direct_attrs         = data_ext_direct
  # render in the order that defines the model

  @direct_attrs        = (def_all_attrs & direct_attrs) | (direct_attrs - def_all_attrs)
  # (data) attributes that are being aliased

  internal_attrs       = @aliased_attrs | @direct_attrs
  # render in the order that defines the model

  @internal_attrs      = (def_all_attrs & internal_attrs) | (internal_attrs - def_all_attrs)
  debug(
    @direct_attrs,
    "(@direct_attrs) attributes that do not require aliasing (data_attrs - data_ext_alias_all)"
  )
  debug(
    @internal_attrs,
    "(@internal_attrs) aliased_attrs | @direct_attrs"
  )
end

def initialize(data, person_parser:, attr_map:, logger: ::Logger.new(IO::NULL))

Parameters:
  • logger (Common::Session::Logger, ::Logger) -- object to manage logs.
  • attr_map (Eco::Data::Mapper) -- mapper to translate attribute names
  • person_parser (Common::People::PersonParser) -- parser/serializer
  • data (Hash, Ecoportal::API::V1::Person) -- `Person` object to

Other tags:
    Note: -
def initialize(data, person_parser:, attr_map:, logger: ::Logger.new(IO::NULL))
  msg = "Constructor needs a PersonParser. Given: #{person_parser.class}"
  raise ArgumentError, msg unless person_parser.is_a?(Eco::API::Common::People::PersonParser)
  msg = "Expecting Mapper object. Given: #{attr_map.class}"
  raise ArgumentError, msg if attr_map && !attr_map.is_a?(Eco::Data::Mapper)
  @source         = data
  @person_parser  = person_parser
  @attr_map       = attr_map
  @logger         = logger
  if parsing?
    @external_entry = data
  else  # SERIALIZING

    @person = data
  end
end

def internal_attrs(data = nil)

Returns:
  • (Array) - all the internally named attributes that the person entry has.
def internal_attrs(data = nil)
  return @internal_attrs unless data || !@internal_attrs
  if parsing?
    init_attr_trackers unless @internal_attrs
    return data.keys & @person_parser.all_model_attrs if data
  else
    @internal_attrs = @person_parser.all_model_attrs
  end
  @internal_attrs
end

def logger

LOGGER
def logger
  @logger || ::Logger.new(IO::NULL)
end

def parsing?

Returns:
  • (Boolean) - returns `true` if we are **parsing**, `false` otherwise.
def parsing?
  !@source.is_a?(Ecoportal::API::V1::Person)
end

def self_mapped_attr?(attr)

Whether the maps include this `attr` as mapped to itself
def self_mapped_attr?(attr)
  return false unless @attr_map
  @attr_map.self_mapped?(attr)
end

def serializing?

Returns:
  • (Boolean) - returns `true` if we are **serializing**, `false` otherwise.
def serializing?
  !parsing?
end

def to_external(value)

Returns:
  • (String, nil, Array - String, nil, Array

Parameters:

  • value (String, Array) -- value(s) to be translated or aliased into external ones.

Other tags:
    Note: -
def to_external(value)
  return value unless @attr_map
  attr = value
  case value
  when Array
    return value.map do |v|
      to_external(v)
    end.compact
  when String
    if @attr_map.internal?(value)
      attr = @attr_map.to_external(value)
    elsif @attr_map.internal?(value.strip)
      unless cached_warning("internal", "spaces", value)
        msg  = "The internal person field name '#{value}' contains "
        msg << "additional spaces in the reference file"
        logger.warn(msg)
      end
      attr = @attr_map.to_external(value.strip)
    elsif [value, value.strip, value.strip.downcase].any? {|val| @attr_map.external?(val)}
      unless cached_warning("internal", "reversed", value)
        msg  = "The mapper [external, internal] attribute names "
        msg << "may be declared reversedly for "
        msg << "INTERNAL attribute: '#{value}'"
        logger.info(msg)
      end
    end
  end
  return nil unless !@external_entry || attributes(@external_entry).include?(attr)
  attr
end

def to_internal(value)

Returns:
  • (String, nil, Array - String, nil, Array

Parameters:

  • value (String, Array) -- value(s) to be translated into internal names.

Other tags:
    Note: -
def to_internal(value)
  # TODO: check PersonEntry#to_internal and #to_external in init_attr_trackers

  # => when attr_map is avoided, it doesn't work as it should

  return value unless @attr_map
  attr = value
  case value
  when Array
    return value.map do |v|
      to_internal(v)
    end.compact
  when String
    if @attr_map.external?(value)
      attr = @attr_map.to_internal(value)
    elsif @attr_map.external?(value.strip)
      unless cached_warning("external", "spaces", value)
        msg  = "The external person field name '#{value}' contains "
        msg << "additional spaces in the reference file"
        logger.warn(msg)
      end
      attr = @attr_map.to_internal(value.strip)
    elsif [value, value.strip, value.strip.downcase].any? {|val| @attr_map.internal?(val)}
      unless cached_warning("external", "reversed", value)
        msg  = "The mapper [external, internal] attribute names "
        msg << "may be declared reversedly for EXTERNAL attribute: '#{value}'"
        logger.info(msg)
      end
    end
  end
  return nil unless @person_parser.all_model_attrs.include?(attr)
end