class Eco::API::Common::People::PersonEntry

Public Class Methods

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

This class is meant to provide 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 (`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 dependencies [Hash] hash where keys are internal attribute names. It is mostly used to deliver final dependencies to attribute parsers/serializers. @param logger [Common::Session::Logger, ::Logger] object to manage logs.

# File lib/eco/api/common/people/person_entry.rb, line 17
def initialize(data, person_parser:, attr_map:, dependencies: {}, 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
  @deps      = dependencies
  @logger    = logger
  @attr_map  = attr_map
  @emap      = PersonEntryAttributeMapper.new(@source, person_parser: @person_parser, attr_map: @attr_map, logger: @logger)

  if parsing?
    @external_entry     = __external_entry(data)
    @mapped_entry       = __mapped_entry(@external_entry)
    @internal_entry     = __internal_entry(@mapped_entry)
    @final_entry        = __final_entry(@internal_entry)
  else  # SERIALIZING
    @person             = data
    @final_entry        = __final_entry(@person)
    @internal_entry     = __internal_entry(@final_entry)
    @mapped_entry       = __mapped_entry(@internal_entry)
    @external_entry     = __external_entry(@mapped_entry)
  end
end

Public Instance Methods

default_tag() click to toggle source
# File lib/eco/api/common/people/person_entry.rb, line 158
def default_tag
  final_entry["default_tag"]
end
default_tag?() click to toggle source
# File lib/eco/api/common/people/person_entry.rb, line 154
def default_tag?
  final_entry.key?("default_tag")
end
email() click to toggle source

@return [String, nil] the email of this person if defined.

# File lib/eco/api/common/people/person_entry.rb, line 117
def email
  final_entry["email"]
end
email?() click to toggle source
# File lib/eco/api/common/people/person_entry.rb, line 121
def email?
  final_entry.key?("email")
end
external_entry() click to toggle source

@note completely serialized entry. @return [Hash] entry `Hash` with external attribute names, and values and types thereof.

# File lib/eco/api/common/people/person_entry.rb, line 50
def external_entry
  @external_entry
end
external_id() click to toggle source

@return [String, nil] the _external id_ of this person if defined.

# File lib/eco/api/common/people/person_entry.rb, line 99
def external_id
  final_entry["external_id"]
end
external_id?() click to toggle source
# File lib/eco/api/common/people/person_entry.rb, line 103
def external_id?
  final_entry.key?("external_id")
end
filter_tags() click to toggle source
# File lib/eco/api/common/people/person_entry.rb, line 138
def filter_tags
  final_entry["filter_tags"] || []
end
filter_tags?() click to toggle source
# File lib/eco/api/common/people/person_entry.rb, line 142
def filter_tags?
  final_entry.key?("filter_tags")
end
final_entry() click to toggle source

@note values ready to be set to a person. @return [Hash] entry `Hash` with internal attribute names, values and types.

# File lib/eco/api/common/people/person_entry.rb, line 67
def final_entry
  @final_entry
end
id() click to toggle source

@return [String, nil] the _internal id_ of this person if defined.

# File lib/eco/api/common/people/person_entry.rb, line 90
def id
  final_entry["id"]
end
id?() click to toggle source
# File lib/eco/api/common/people/person_entry.rb, line 94
def id?
  final_entry.key?("id")
end
identify() click to toggle source

Provides a reference to this person entry. @return [String] string summary of this person identity.

# File lib/eco/api/common/people/person_entry.rb, line 164
def identify
  str_id = id ? "id: '#{id}'; " : ""
  "(row: #{idx}) '#{name}' (#{str_id}ext_id: '#{external_id}'; email: '#{email}')"
end
idx() click to toggle source

@note `Eco::API::Common::People::EntryFactory#entries` adds this `idx` (i.e. row number) @return [Integer] the entry number in the input file

# File lib/eco/api/common/people/person_entry.rb, line 85
def idx
  final_entry["idx"]
end
internal_entry() click to toggle source

@note just one step away from being completely parsed (only types parsing pending). @return [Hash] entry `Hash` with internal attribute names and values, but external types.

# File lib/eco/api/common/people/person_entry.rb, line 56
def internal_entry
  @internal_entry
end
mapped_entry() click to toggle source

@return [Hash] entry `Hash` with internal attribute names, but external types and values.

# File lib/eco/api/common/people/person_entry.rb, line 61
def mapped_entry
  @mapped_entry
end
name() click to toggle source

@return [String, nil] the name of this person if defined.

# File lib/eco/api/common/people/person_entry.rb, line 108
def name
  final_entry["name"]
end
name?() click to toggle source
# File lib/eco/api/common/people/person_entry.rb, line 112
def name?
  final_entry.key?("name")
end
new(data) click to toggle source

Generates a new entry @return [PersonEntry]

# File lib/eco/api/common/people/person_entry.rb, line 44
def new(data)
  self.class.new(data, person_parser: @person_parser, attr_map: @attr_map, dependencies: @deps, logger: @logger)
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.rb, line 73
def parsing?
  !@source.is_a?(Ecoportal::API::Internal::Person)
end
policy_group_ids() click to toggle source
# File lib/eco/api/common/people/person_entry.rb, line 146
def policy_group_ids
  final_entry["policy_group_ids"] || []
end
policy_group_ids?() click to toggle source
# File lib/eco/api/common/people/person_entry.rb, line 150
def policy_group_ids?
  final_entry.key?("policy_group_ids")
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.rb, line 79
def serializing?
  !parsing?
end
set_account(person, exclude: nil) click to toggle source

Setter to fill in the `account` properties of the `Person` that are present in the `Entry`. @note it only sets those account properties defined in the entry.

Meaning that if an account property is not present in the entry, this will not be set on the target person.

@param person [Ecoportal::API::Internal::Person] the person we want to set the account values to. @param exclude [String, Array<String>] account properties that should not be set/changed to the person.

# File lib/eco/api/common/people/person_entry.rb, line 211
def set_account(person, exclude: nil)
  person.account = {} if !person.account
  scoped_attrs = @emap.account_attrs(@final_entry) - into_a(exclude)
  @final_entry.slice(*scoped_attrs).each do |attr, value|
    set_part(person.account, attr, value)
  end
end
set_core(person, exclude: nil) click to toggle source

Setter to fill in all the `core` properties of the `Person` that are present in the `Entry`. @note

1. it only sets those core properties defined in the entry.
  Meaning that if an core property is not present in the entry, this will not be set on the target person.
2. if there's an incorrect email exception, it blanks the email and logs a warning message

@param person [Ecoportal::API::V1::Person] the person we want to set the core values to. @param exclude [String, Array<String>] core attributes that should not be set/changed to the person.

# File lib/eco/api/common/people/person_entry.rb, line 190
def set_core(person, exclude: nil)
  scoped_attrs = @emap.core_attrs(@final_entry) - into_a(exclude)
  @final_entry.slice(*scoped_attrs).each do |attr, value|
    begin
      set_part(person, attr, value)
    rescue Exception => e
      if attr == "email"
        logger.error(e.to_s + " - setting blank email instead.")
        set_part(person, attr, nil)
      else
        raise
      end
    end
  end
end
set_details(person, exclude: nil) click to toggle source

TO DO: use person.details.schema_id to switch @emap and @person_parser (or just crash if they don't match?) Setter to fill in all the schema `details` fields of the `Person` that are present in the `Entry`. @note it only sets those details properties defined in the entry.

Meaning that if an details property is not present in the entry, this will not be set on the target person.

@param person [Ecoportal::API::V1::Person] the person we want to set the schema fields' values to. @param exclude [String, Array<String>] schema field attributes that should not be set/changed to the person.

# File lib/eco/api/common/people/person_entry.rb, line 225
def set_details(person, exclude: nil)
  person.add_details(@person_parser.schema) if !person.details || !person.details.schema_id
  scoped_attrs = @emap.details_attrs(@final_entry) - into_a(exclude)
  @final_entry.slice(*scoped_attrs).each do |attr, value|
    set_part(person.details, attr, value)
  end
end
supervisor_id() click to toggle source

@return [String, nil] the _supervisor id_ of this person if defined.

# File lib/eco/api/common/people/person_entry.rb, line 126
def supervisor_id
  final_entry["supervisor_id"]
end
supervisor_id=(value) click to toggle source
# File lib/eco/api/common/people/person_entry.rb, line 130
def supervisor_id=(value)
  final_entry["supervisor_id"] = value
end
supervisor_id?() click to toggle source
# File lib/eco/api/common/people/person_entry.rb, line 134
def supervisor_id?
  final_entry.key?("supervisor_id")
end
to_s(options) click to toggle source

Provides a reference of this person entry. @return [String] string summary of this person identity.

# File lib/eco/api/common/people/person_entry.rb, line 171
def to_s(options)
  options = into_a(options)
  case
  when options.include?(:identify)
    identify
  else
    final_entry.each.map do |k, v|
      "'#{k}': '#{v.to_json}'"
    end.join(" | ")
  end
end

Private Instance Methods

__external_entry(data) click to toggle source

@return [Hash] entry in raw: that with external names, values and types.

# File lib/eco/api/common/people/person_entry.rb, line 236
def __external_entry(data)
  return data if parsing?
  _external_serializing(data)
end
__final_entry(data) click to toggle source

@return [Hash] that with internal names, values and types.

# File lib/eco/api/common/people/person_entry.rb, line 254
def __final_entry(data)
  return _final_parsing(data) if parsing?
  _final_serializing(data)
end
__internal_entry(data) click to toggle source

@return [Hash] that with internal names and values, but external values and types.

# File lib/eco/api/common/people/person_entry.rb, line 248
def __internal_entry(data)
  return _internal_parsing(data) if parsing?
  _internal_serializing(data)
end
__mapped_entry(data) click to toggle source

@return [Hash] that with internal names but external values and types.

# File lib/eco/api/common/people/person_entry.rb, line 242
def __mapped_entry(data)
  return _mapped_parsing(data) if parsing?
  _mapped_serializing(data)
end
_external_serializing(mapped_entry) click to toggle source

Serializing helper that maps internal attributes to external attribute names @note Serialize: here we unaliase internal attribute names into external ones. @param mapped_entry [Hash] that with internal names but external values and types. @return [Hash] with external names, values and types.

# File lib/eco/api/common/people/person_entry.rb, line 263
def _external_serializing(mapped_entry)
  target_attrs    = @emap.all_model_attrs | @emap.aliased_attrs
  rest_keys       = mapped_entry.keys - target_attrs
  target_attrs   -= ["send_invites"]
  external_entry  = target_attrs.each_with_object({}) do |attr, hash|
    unless hash.key?(ext_attr = @emap.to_external(attr))
      hash[ext_attr] = mapped_entry[attr]
    end
  end
  merge_missing_attrs(external_entry, mapped_entry.slice(*rest_keys))
end
_final_parsing(internal_entry) click to toggle source

Parsing helper where attributes with custom parsers are already parsed, but it finishes to parse the types (i.e. to `Array` if `multiple`) @param internal_entry [Hash] the entry with the internal attribute names and values but the external types. @return [Hash] the `parsed entry` with the internal final attributes names, values and types.

# File lib/eco/api/common/people/person_entry.rb, line 328
def _final_parsing(internal_entry)
  core_account_attrs = @emap.account_attrs(internal_entry) + @emap.core_attrs(internal_entry)
  core_account_hash  = internal_entry.slice(*core_account_attrs).each_with_object({}) do |(attr, value), hash|
    hash[attr]  = _parse_type(attr, value)
  end

  details_attrs     = @emap.details_attrs(internal_entry)
  details_hash      = internal_entry.slice(*details_attrs).each_with_object({}) do |(attr, value), hash|
    hash[attr] = _parse_type(attr, value, schema: @person_parser.schema)
  end

  merging(core_account_hash, details_hash) do |final_entry|
    final_entry = merge_missing_attrs(final_entry, internal_entry)
    final_entry.merge(_parse_values(final_entry, :final))
  end
end
_final_serializing(person) click to toggle source

Serializing helper that just creates the _parsed entry_ out of a `Person` object. @note

- when unnesting attributes, the overriding precedence for collisions is
  - `core` -> _overrides_ -> `account` -> _overrides_ -> `details`
- to keep things consistent, the `internal entry` hash has keys in this order:
  - `core`, `account`, `details`.

@param person [Ecoportal::API::V1::Person] the `Person` object to transform into a _parsed entry_. @return [Hash] the `parsed entry` with the internal attributes names and internal typed values.

# File lib/eco/api/common/people/person_entry.rb, line 353
def _final_serializing(person)
  core_hash    = @person_parser.target_attrs_core.reduce({}) do |hash, attr|
    hash.merge(hash_attr(attr, get_part(person, attr)))
  end
  account_hash = @person_parser.target_attrs_account.reduce({}) do |hash, attr|
    hash.merge(hash_attr(attr, get_part(person.account, attr)))
  end
  details_hash = @person_parser.target_attrs_details.reduce({}) do |hash, attr|
    hash.merge(hash_attr(attr, get_part(person.details, attr)))
  end
  merging(core_hash, account_hash, details_hash) do |final_entry|
    final_entry["Has account?"] = !!person.account
    final_entry.merge(_serialize_values(person, :person))
  end
end
_internal_parsing(mapped_entry) click to toggle source

Parsing helper that just **parses the values** that have a parser/serializer defined. @note this entry will still miss the type parsing (i.e. to `Array` if `multiple`) @param mapped_entry [Hash] the entry with the _internal attribute_ names but the _external values_. @return [Hash] the `internal entry` with the internal attributes names and values.

# File lib/eco/api/common/people/person_entry.rb, line 303
def _internal_parsing(mapped_entry)
  mapped_entry.merge(_parse_values(mapped_entry, :internal))
end
_internal_serializing(final_entry) click to toggle source

Serializing helper that just creates the _internal entry_ out of a _parsed entry_ (serializes the type). @param final_entry [Hash] the entry with all internal (attributes, values and types) @return [Hash] the `internal entry` with the internal attributes names and values, but external types.

# File lib/eco/api/common/people/person_entry.rb, line 310
def _internal_serializing(final_entry)
  final_entry        = final_entry.merge(_serialize_values(final_entry, :final))
  core_account       = @person_parser.target_attrs_account + @person_parser.target_attrs_core
  core_account_hash  = core_account.reduce({}) do |hash, attr|
    hash.merge(hash_attr(attr, _serialize_type(attr, final_entry[attr])))
  end
  details_hash       = @person_parser.target_attrs_details.reduce({}) do |hash, attr|
    hash.merge(hash_attr(attr, _serialize_type(attr, final_entry[attr], schema: @person_parser.schema)))
  end
  merging(core_account_hash, details_hash) do |internal_entry|
    merge_missing_attrs(internal_entry, final_entry)
  end
end
_mapped_parsing(external_entry) click to toggle source

Parsing helper that aliases attribute names (from internal to external names) @note Parse: here we aliase external attribute names into internal ones. @param external_entry [Hash] entry in raw, with external names and values. @return [Hash] entry with internal names, but still external values and types.

# File lib/eco/api/common/people/person_entry.rb, line 279
def _mapped_parsing(external_entry)
  mapped_hash = @emap.aliased_attrs.each_with_object({}) do |attr, hash|
    hash[attr] = external_entry[@emap.to_external(attr)]
  end
  external_entry.slice(*@emap.direct_attrs).merge(mapped_hash)
end
_mapped_serializing(internal_entry) click to toggle source

Serializing helper that **serializes values** that have a parser/serializer defined. @note Serializing:

1. here we tranform internal into external **values**.
2. when running the serializers, it overrides existing keys.

@param internal_entry [Hash] entry with internal names and values, but external types. @return [Hash] entry with internal names and external values and types.

# File lib/eco/api/common/people/person_entry.rb, line 292
def _mapped_serializing(internal_entry)
  mapped_hash = internal_entry.merge(_serialize_values(internal_entry, :internal))
  model_attrs = @person_parser.all_model_attrs - ["send_invites"]
  aux_hash    = mapped_hash.slice(*model_attrs)
  merge_missing_attrs(aux_hash, mapped_hash)
end
_parse_type(attr, value, schema: nil) click to toggle source

Transforms each `String` value into its typed version

# File lib/eco/api/common/people/person_entry.rb, line 409
def _parse_type(attr, value, schema: nil)
  value = value.strip if value.is_a?(String)
  value = nil         if value.to_s.strip.empty?
  case
  when !!schema
    unless field = schema[attr]
      fatal("Field '#{attr}' does not exist in details of schema: '#{schema.name}'")
    end
    value = @person_parser.parse(:multiple, value) if field.multiple

    if @person_parser.defined?(field.type.to_sym)
      value = @person_parser.parse(field.type.to_sym, value, deps: {"attr" => attr})
    end
    value
  when attr == "email"
    value = value.strip.downcase if value
    value
  when ["policy_group_ids", "filter_tags", "login_provider_ids", "starred_ids"].include?(attr)
    value = @person_parser.parse(:multiple, value)
    value = (attr == "filter_tags")? value.compact.map(&:upcase) : value
    value
  when ["freemium", "accept_eula"].include?(attr)
    @person_parser.parse(:boolean, value)
  when ["subordinates"].include?(attr)
    @person_parser.parse(:number, value)
  else
    value
  end
end
_parse_values(entry, phase = :internal) click to toggle source
# File lib/eco/api/common/people/person_entry.rb, line 378
def _parse_values(entry, phase = :internal)
  @person_parser.active_attrs(entry, phase).each_with_object({}) do |attr, hash|
    parsed_attr = @person_parser.parse(attr, entry.merge(hash), phase)
    hash.merge!(hash_attr(attr, parsed_attr))
  end
end
_serialize_type(attr, value, schema: nil) click to toggle source

Transforms each typed value into its `String` version

# File lib/eco/api/common/people/person_entry.rb, line 386
def _serialize_type(attr, value, schema: nil)
  case
  when !!schema
    unless field = schema[attr]
      fatal("Field '#{attr}' does not exist in details of schema: '#{schema.name}'")
    end
    value = @person_parser.serialize(:multiple, value) if field.multiple
    if @person_parser.defined?(field.type.to_sym)
      value = @person_parser.serialize(field.type.to_sym, value, deps: {"attr" => attr})
    end
    value
  when ["policy_group_ids", "filter_tags", "login_provider_ids", "starred_ids"].include?(attr)
    @person_parser.serialize(:multiple, value)
  when ["freemium", "accept_eula"].include?(attr)
    @person_parser.serialize(:boolean, value)
  when ["subordinates"].include?(attr)
    @person_parser.serialize(:number, value)
  else
    value
  end
end
_serialize_values(entry, phase = :person) click to toggle source

HELPERS

# File lib/eco/api/common/people/person_entry.rb, line 370
def _serialize_values(entry, phase = :person)
  @person_parser.active_attrs(entry, phase, process: :serialize).each_with_object({}) do |attr, hash|
    data        = entry.is_a?(Hash)? entry.merge(hash) : entry
    serial_attr = @person_parser.serialize(attr, data, phase, deps: @deps[attr] || {})
    hash.merge!(hash_attr(attr, serial_attr))
  end
end
fatal(msg) click to toggle source
# File lib/eco/api/common/people/person_entry.rb, line 505
def fatal(msg)
  logger.fatal(msg)
  raise msg
end
get_part(obj, attr) click to toggle source
# File lib/eco/api/common/people/person_entry.rb, line 463
def get_part(obj, attr)
  return unless obj
  case obj
  when Ecoportal::API::V1::PersonDetails
    #unless field = obj.get_field(attr)
    #  fatal("Field '#{attr}' does not exist in details of schema: '#{obj.schema_id}'")
    #end
    obj[attr]
  else
    obj.send(attr)
  end
end
hash_attr(attr, value) click to toggle source

@return [Hash] `value` if it was a `Hash`, and `{ attr => value}` otherwise

# File lib/eco/api/common/people/person_entry.rb, line 495
def hash_attr(attr, value)
  return value if value.is_a?(Hash)
  { attr => value }
end
into_a(value) click to toggle source
# File lib/eco/api/common/people/person_entry.rb, line 457
def into_a(value)
  value = [] if value == nil
  value = [].push(value) unless value.is_a?(Array)
  value
end
logger() click to toggle source

LOGGER

# File lib/eco/api/common/people/person_entry.rb, line 501
def logger
  @logger || ::Logger.new(IO::NULL)
end
merge_missing_attrs(dest_entry, source_entry) click to toggle source

Adds to `dest_entry` the `keys` it misses from `source_entry`

# File lib/eco/api/common/people/person_entry.rb, line 452
def merge_missing_attrs(dest_entry, source_entry)
  keys_rest = source_entry.keys - dest_entry.keys
  dest_entry.merge(source_entry.slice(*keys_rest))
end
merging(*hashes) { |merged| ... } click to toggle source

Merges multiple hashes giving overriding perference to the first ones. @return [Hash] with well sorted keys, as they came in the order of the input hashes.

# File lib/eco/api/common/people/person_entry.rb, line 441
def merging(*hashes)
  sorted_keys = hashes.map {|h| h.keys}.flatten.uniq
  rev_hash    = hashes.reverse.each_with_object({}) {|h, out| out.merge!(h)}
  merged      = sorted_keys.each_with_object({}) do |k, h|
    h[k] = rev_hash[k]
  end
  merged      = yield(merged) if block_given?
  merged
end
print_models() click to toggle source

Function to debug faste

set_part(obj, attr, value) click to toggle source
# File lib/eco/api/common/people/person_entry.rb, line 476
def set_part(obj, attr, value)
  return unless obj
  begin
    case obj
    when Ecoportal::API::V1::PersonDetails
      unless field = obj.get_field(attr)
        fatal("Field '#{attr}' does not exist in details of schema: '#{obj.schema_id}'")
      end
      obj[attr] = value
    else
      obj.send("#{attr}=", value)
    end
  rescue Exception => e
    # add more info to the error
    raise e.append_message " -- Entry #{to_s(:identify)}"
  end
end