module Eco module API module Common module People # @attr_reader core_attrs [Array] core attributes that are present in the person entry. # @attr_reader details_attrs [Array] schema details attributes that are present in the person entry. # @attr_reader account_attrs [Array] account attributes that are present in the person entry. # @attr_reader all_attrs [Array] all the attrs that are present in the person entry. # @attr_reader internal_attrs [Array] all the internally named attributes that the person entry has. # @attr_reader aliased_attrs [Array] only those internal attributes present in the person entry that have an internal/external name mapping. # @attr_reader direct_attrs [Array] only those internal attributes present in the person entry that do **not** have an internal/external name mapping. class PersonEntryAttributeMapper @@cached_warnings = {} attr_reader :core_attrs, :details_attrs, :account_attrs, :all_attrs attr_reader :internal_attrs, :aliased_attrs, :direct_attrs # 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, Person] `Person` object to be serialized or hashed entry (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. 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 init_attr_trackers else # SERIALIZING @person = data @internal_attrs = @person_parser.all_attrs end @core_attrs = @person_parser.target_attrs_core(@internal_attrs) @details_attrs = @person_parser.target_attrs_details(@internal_attrs) @account_attrs = @person_parser.target_attrs_account(@internal_attrs) @all_attrs = @core_attrs | @account_attrs | @details_attrs end # To know if currently the object is in parse or serialize mode. # @return [Boolean] returns `true` if we are **parsing**, `false` otherwise. def parsing? !@source.is_a?(Ecoportal::API::Internal::Person) end # To know if currently the object is in parse or serialize mode. # @return [Boolean] returns `true` if we are **serializing**, `false` otherwise. def serializing? !parsing? end # 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] value(s) to be translated into internal names. # @return [String, nil, Array] value(s) to be translated or aliased into external ones. # @return [String, nil, Array external attributes int_aliased = @person_parser.all_attrs.select { |attr| to_external(attr) } ext_alias = int_aliased.map { |attr| to_external(attr) } # virtual attrs (non native internal attr that require aliasing): ext_vi_aliased = attributes(@external_entry).select do |attr| !ext_alias.include?(attr) && @attr_map&.external?(attr) end # internal name of external attrs that are not native internal attrs int_vi_aliased = ext_vi_aliased.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_attrs.include?(attr) @attr_map.to_internal(attr) end.compact @aliased_attrs = int_aliased + int_vi_aliased int_unlinked = @person_parser.undefined_attrs.select { |attr| !to_external(attr) } # those with parser or alias: int_linked = @person_parser.all_attrs - int_unlinked ext_aliased = ext_alias + ext_vi_aliased # those that are direct external to internal: ext_direct = attributes(@external_entry) - ext_aliased # to avoid collisions between internal names: @direct_attrs = ext_direct - int_linked @internal_attrs = @aliased_attrs | @direct_attrs end def attributes(value) case value when CSV::Row value&.headers when Hash value&.keys when PersonEntry @person_parser.target_attrs_core else [] end end # LOGGER def logger @logger || ::Logger.new(IO::NULL) end 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 def fatal(msg) logger.fatal(msg) exit end end end end end end