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.

Attributes

aliased_attrs[R]
direct_attrs[R]

Public Class Methods

new(data, person_parser:, attr_map:, logger: ::Logger.new(IO::NULL)) click to toggle source

Helper class tied to `PersonEntry` that allows to track which attributes of a person entry are present and how they should be mapped between internal and external names if applicable. This class is meant to help in providing a common interface to access entries of source data that come in different formats. @note

- if `data` is a `Person` object, its behaviour is `serialise`.
- if `data` is **not** a `Person` object, it does a `parse`.
- currently **in rework**, so there may be subtle differences that make it temporarily unstable (yet it is reliable).

@param data [Hash, Ecoportal::API::V1::Person] `Person` object to be serialized or hashed entry to be parsed (note: `CSV::Row` is accepted). @param person_parser [Common::People::PersonParser] parser/serializer of person attributes (it contains a set of attribute parsers). @param attr_map [Eco::Data::Mapper] mapper to translate attribute names from external to internal names and _vice versa_. @param logger [Common::Session::Logger, ::Logger] object to manage logs.

# File lib/eco/api/common/people/person_entry_attribute_mapper.rb, line 23
def initialize(data, person_parser:, attr_map:, logger: ::Logger.new(IO::NULL))
  raise "Constructor needs a PersonParser. Given: #{parser}" if !person_parser.is_a?(Eco::API::Common::People::PersonParser)
  raise "Expecting Mapper object. Given: #{attr_map}" 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

Public Instance Methods

account_attrs(data = nil) click to toggle source

@return [Array<String>] account attributes that are present in the person entry.

# File lib/eco/api/common/people/person_entry_attribute_mapper.rb, line 87
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
all_model_attrs(data = nil) click to toggle source

@return [Array<String>] all the attrs that are present in the person entry.

# File lib/eco/api/common/people/person_entry_attribute_mapper.rb, line 66
def all_model_attrs(data = nil)
  core_attrs(data) | account_attrs(data) | details_attrs(data)
end
core_attrs(data = nil) click to toggle source

@return [Array<String>] core attributes that are present in the person entry.

# File lib/eco/api/common/people/person_entry_attribute_mapper.rb, line 71
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
details_attrs(data = nil) click to toggle source

@return [Array<String>] schema details attributes that are present in the person entry.

# File lib/eco/api/common/people/person_entry_attribute_mapper.rb, line 79
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
internal_attrs(data = nil) click to toggle source

@return [Array<String>] all the internally named attributes that the person entry has.

# File lib/eco/api/common/people/person_entry_attribute_mapper.rb, line 51
def internal_attrs(data = nil)
  return @internal_attrs unless data || !@internal_attrs
  if parsing?
    init_attr_trackers unless @internal_attrs
    if data
      return data.keys & @person_parser.all_model_attrs
    end
  else
    @internal_attrs = @person_parser.all_model_attrs
  end
  @internal_attrs
end
parsing?() click to toggle source

To know if currently the object is in parse or serialize mode. @return [Boolean] returns `true` if we are parsing, `false` otherwise.

# File lib/eco/api/common/people/person_entry_attribute_mapper.rb, line 96
def parsing?
  !@source.is_a?(Ecoportal::API::V1::Person)
end
serializing?() click to toggle source

To know if currently the object is in parse or serialize mode. @return [Boolean] returns `true` if we are serializing, `false` otherwise.

# File lib/eco/api/common/people/person_entry_attribute_mapper.rb, line 102
def serializing?
  !parsing?
end
to_external(value) click to toggle source

Serializing helper also used to do a reverse mapping when parsing:

- as there could be _internal attributes_ that shared _external attributes_,
- when parsing, you use this helper to recognize the source _external attribute_ of each _internal_ one.

If there no `mapper` defined for the object, it mirrors `value`. If there is a `mapper` defined for the object:

1. if the `value` exists as _internal-, translates it into an _external_ one.
2. if it doesn't exist, returns `nil`.

@note

1. the **scope of attributes** is based on all the attributes defined in the current entry.
2. the attributes recognized by the person parser are those of of the `Person` model (where details attributes depend on the `schema`).

@param value [String, Array<String>] value(s) to be translated or aliased into external ones. @return [String, nil, Array<String] the external name(s) of `value`.

# File lib/eco/api/common/people/person_entry_attribute_mapper.rb, line 156
def to_external(value)
  return value if !@attr_map
  attr = value
  case value
  when Array
    return value.map do |v|
      to_external(v)
    end.compact
  when String
    case
    when @attr_map.internal?(value)
      attr = @attr_map.to_external(value)
    when @attr_map.internal?(value.strip)
      unless cached_warning("internal", "spaces", value)
        logger.warn("The internal person field name '#{value}' contains additional spaces in the reference file")
      end
      attr = @attr_map.to_external(value.strip)
    when @attr_map.external?(value) || @attr_map.external?(value.strip) || @attr_map.external?(value.strip.downcase)
      unless cached_warning("internal", "reversed", value)
        logger.warn("The mapper [external, internal] attribute names may be declared reversedly for INTERNAL attribute: '#{value}'")
      end
    end
  end

  return nil unless !@external_entry || attributes(@external_entry).include?(attr)
  attr
end
to_internal(value) click to toggle source

If there no `mapper` defined for the object, it mirrors `value`. If there is a `mapper` defined for the object:

1. if the `value` exists as external, translates it into an internal one.
2. if it doesn't exist, returns `nil`.

@note

1. the **scope of attributes** is based on all the attributes recognized by the person parser.
2. the attributes recognized by the person parser are those of of the `Person` model (where details attributes depend on the `schema`).

@param value [String, Array<String>] value(s) to be translated into internal names. @return [String, nil, Array<String] the internal name(s) of `value`.

# File lib/eco/api/common/people/person_entry_attribute_mapper.rb, line 115
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 if !@attr_map
  attr = value
  case value
  when Array
    return value.map do |v|
      to_internal(v)
    end.compact
  when String
    case
    when @attr_map.external?(value)
      attr = @attr_map.to_internal(value)
    when @attr_map.external?(value.strip)
      unless cached_warning("external", "spaces", value)
        logger.warn("The external person field name '#{value}' contains additional spaces in the reference file")
      end
      attr = @attr_map.to_internal(value.strip)
    when @attr_map.internal?(value) || @attr_map.internal?(value.strip) || @attr_map.internal?(value.strip.downcase)
      unless cached_warning("external", "reversed", value)
        logger.warn("The mapper [external, internal] attribute names may be declared reversedly for EXTERNAL attribute: '#{value}'")
      end
    end
  end

  return nil unless @person_parser.all_model_attrs.include?(attr)
end

Private Instance Methods

attributes(value) click to toggle source
# File lib/eco/api/common/people/person_entry_attribute_mapper.rb, line 237
def attributes(value)
  case value
  when CSV::Row
    value&.headers
  when Hash
    value&.keys
  when PersonEntry
    @person_parser.target_attrs_core
  else
    []
  end
end
cached_warning(*args) click to toggle source
# File lib/eco/api/common/people/person_entry_attribute_mapper.rb, line 255
def cached_warning(*args)
  unless exists = !!@@cached_warnings.dig(*args)
    args.reduce(@@cached_warnings) do |cache, level|
      cache[level] = {} if !cache.key?(level)
      cache[level]
    end
  end
  exists
end
fatal(msg) click to toggle source
# File lib/eco/api/common/people/person_entry_attribute_mapper.rb, line 265
def fatal(msg)
  logger.fatal(msg)
  raise msg
end
init_attr_trackers() click to toggle source

when parsing:

# File lib/eco/api/common/people/person_entry_attribute_mapper.rb, line 187
def init_attr_trackers
  # (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.select { |attr| !to_external(attr) }
  # (def) those with parser or alias:
  def_linked        = def_all_attrs - def_unlinked
  # (data) data attributes (actual attributes of the entry)
  data_attrs        = attributes(@external_entry)
  # (data) attributes of the data that come directly as internal attribute names
  data_direct_attrs = data_attrs & def_all_attrs

  # (def) configured  as alised (internal <-> external attributes)
  def_int_aliased   = def_all_attrs.select  { |attr| to_external(attr) }
  def_ext_alias     = def_int_aliased.map   { |attr| to_external(attr) }

  # (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) attrs that could 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_def_ext_alias   = data_def_int_aliased.map { |attr| to_external(attr) }
  # (data) actual attrs of the data that come aliased
  data_ext_alias       = data_def_ext_alias & data_attrs

  # (data) all internal attributes that could come aliased, with given the entry
  @aliased_attrs       = data_def_int_aliased + data_vi_int_aliased
  # (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

  # 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 - def_linked
  @direct_attrs        = data_ext_direct
  @internal_attrs      = @aliased_attrs | @direct_attrs
end
logger() click to toggle source

LOGGER

# File lib/eco/api/common/people/person_entry_attribute_mapper.rb, line 251
def logger
  @logger || ::Logger.new(IO::NULL)
end