class Eco::API::Common::People::PersonEntry
Public Class Methods
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
# File lib/eco/api/common/people/person_entry.rb, line 158 def default_tag final_entry["default_tag"] end
# File lib/eco/api/common/people/person_entry.rb, line 154 def default_tag? final_entry.key?("default_tag") end
@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
# File lib/eco/api/common/people/person_entry.rb, line 121 def email? final_entry.key?("email") end
@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
@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
# File lib/eco/api/common/people/person_entry.rb, line 103 def external_id? final_entry.key?("external_id") end
@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
@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
# File lib/eco/api/common/people/person_entry.rb, line 94 def id? final_entry.key?("id") end
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
@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
@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
@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
@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
# File lib/eco/api/common/people/person_entry.rb, line 112 def name? final_entry.key?("name") end
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
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
# File lib/eco/api/common/people/person_entry.rb, line 146 def policy_group_ids final_entry["policy_group_ids"] || [] end
# File lib/eco/api/common/people/person_entry.rb, line 150 def policy_group_ids? final_entry.key?("policy_group_ids") end
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
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
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
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
@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
# File lib/eco/api/common/people/person_entry.rb, line 130 def supervisor_id=(value) final_entry["supervisor_id"] = value end
# File lib/eco/api/common/people/person_entry.rb, line 134 def supervisor_id? final_entry.key?("supervisor_id") end
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
@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
@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
@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
@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
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
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
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
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
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
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
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
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
# 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
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
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
# File lib/eco/api/common/people/person_entry.rb, line 505 def fatal(msg) logger.fatal(msg) raise msg end
# 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
@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
# 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
# File lib/eco/api/common/people/person_entry.rb, line 501 def logger @logger || ::Logger.new(IO::NULL) end
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
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
Function to debug faste
# File lib/eco/api/common/people/person_entry.rb, line 511 def print_models print_it = Proc.new do |name, model| puts "#{name}:" pp model puts "*" * 30 end fin = Proc.new {|x_| print_it.call("final_entry", @final_entry) } int = Proc.new {|x_| print_it.call("internal_entry", @internal_entry) } mad = Proc.new {|x_| print_it.call("mapped_entry", @mapped_entry) } ext = Proc.new {|x_| print_it.call("external_entry", @external_entry) } call_order = parsing? ? [ext, mad, int, fin] : [fin, int, mad, ext] call_order.each {|proc| proc.call} end
# 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