lib/eco/api/common/people/person_entry.rb in eco-helpers-2.6.4 vs lib/eco/api/common/people/person_entry.rb in eco-helpers-2.7.0

- old
+ new

@@ -8,26 +8,39 @@ # 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. + # @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 managelogs. 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) + msg = "Constructor needs a PersonParser. Given: #{person_parser.class}" + raise msg unless person_parser.is_a?(Eco::API::Common::People::PersonParser) - @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) + msg = "Expecting Mapper object. Given: #{attr_map.class}" + raise msg 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) @@ -37,39 +50,45 @@ @internal_entry = __internal_entry(@final_entry) @mapped_entry = __mapped_entry(@internal_entry) @external_entry = __external_entry(@mapped_entry) end - (print_models; exit(1)) if DEBUG + (print_models; exit(1)) if DEBUG # rubocop:disable Style/Semicolon end # Generates a new entry # @return [PersonEntry] def new(data) - self.class.new(data, person_parser: @person_parser, attr_map: @attr_map, dependencies: @deps, logger: @logger) + self.class.new( + data, + person_parser: @person_parser, + attr_map: @attr_map, + dependencies: @deps, + logger: @logger + ) end # @note completely serialized entry. # @return [Hash] entry `Hash` with **external** attribute names, and values and types thereof. - def external_entry + def external_entry # rubocop:disable Style/TrivialAccessors @external_entry 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. - def internal_entry + def internal_entry # rubocop:disable Style/TrivialAccessors @internal_entry end # @return [Hash] entry `Hash` with **internal** attribute names, but **external** types and values. - def mapped_entry + def mapped_entry # rubocop:disable Style/TrivialAccessors @mapped_entry end # @note values ready to be set to a person. # @return [Hash] entry `Hash` with **internal** attribute names, values and types. - def final_entry + def final_entry # rubocop:disable Style/TrivialAccessors @final_entry end # To know if currently the object is in parse or serialize mode. # @return [Boolean] returns `true` if we are **parsing**, `false` otherwise. @@ -171,12 +190,11 @@ # Provides a reference of this person entry. # @return [String] string summary of this person identity. def to_s(options) options = into_a(options) - case - when options.include?(:identify) + if options.include?(:identify) identify else final_entry.each.map do |k, v| "'#{k}': '#{v.to_json}'" end.join(" | ") @@ -191,30 +209,25 @@ # @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. 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 + set_part(person, attr, value) + rescue StandardError => e + raise unless attr == "email" + logger.error("#{e} - setting blank email instead.") + set_part(person, attr, nil) end 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. def set_account(person, exclude: nil) - person.account = {} if !person.account + person.account = {} unless 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 @@ -314,26 +327,27 @@ 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| + 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 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. + # @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. 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) + 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) @@ -352,21 +366,21 @@ # - 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. def _final_serializing(person) - core_hash = @person_parser.target_attrs_core.reduce({}) do |hash, attr| + 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["Has account?"] = !person.account.nil? final_entry.merge(_serialize_values(person, :person)) end end # HELPERS @@ -385,82 +399,80 @@ end end # Transforms each **typed** value into its `String` version def _serialize_type(attr, value, schema: nil) - case - when !!schema - unless field = schema[attr] + if !!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) + elsif %w[policy_group_ids filter_tags login_provider_ids starred_ids].include?(attr) @person_parser.serialize(:multiple, value) - when ["freemium", "accept_eula"].include?(attr) + elsif %w[freemium accept_eula].include?(attr) @person_parser.serialize(:boolean, value) - when ["subordinates"].include?(attr) + elsif ["subordinates"].include?(attr) @person_parser.serialize(:number, value) else value end end # Transforms each `String` value into its **typed** version - def _parse_type(attr, value, schema: nil) + def _parse_type(attr, value, schema: nil) # rubocop:disable Metrics/AbcSize value = value.strip if value.is_a?(String) value = nil if value.to_s.strip.empty? - case - when !!schema - unless field = schema[attr] + if !!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" + elsif attr == "email" value = value.strip.downcase if value value - when ["policy_group_ids", "filter_tags", "login_provider_ids", "starred_ids"].include?(attr) + elsif %w[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 = value.compact.map(&:upcase) if attr == "filter_tags" value - when ["freemium", "accept_eula"].include?(attr) + elsif %w[freemium accept_eula].include?(attr) @person_parser.parse(:boolean, value) - when ["subordinates"].include?(attr) + elsif ["subordinates"].include?(attr) @person_parser.parse(:number, value) else value end 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. def merging(*hashes) - sorted_keys = hashes.map {|h| h.keys}.flatten.uniq + sorted_keys = hashes.map(&: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 = yield(merged) if block_given? merged end # Adds to `dest_entry` the `keys` it misses from `source_entry` 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 def into_a(value) - value = [] if value == nil + value = [] if value.nil? value = [].push(value) unless value.is_a?(Array) value end def get_part(obj, attr) @@ -476,24 +488,23 @@ end end 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)}" + + case obj + when Ecoportal::API::V1::PersonDetails + msg = "Field '#{attr}' does not exist in details of schema: '#{obj.schema_id}'" + fatal msg unless obj.get_field(attr) + + obj[attr] = value + else + obj.send("#{attr}=", value) end + rescue StandardError => e + # add more info to the error + raise e.append_message " -- Entry #{to_s(:identify)}" end # @return [Hash] `value` if it was a `Hash`, and `{ attr => value}` otherwise def hash_attr(attr, value) return value if value.is_a?(Hash) @@ -510,22 +521,22 @@ raise msg end # Function to debug faste def print_models - print_it = Proc.new do |name, model| + print_it = proc 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) } + fin = proc { print_it.call("final_entry", @final_entry) } + int = proc { print_it.call("internal_entry", @internal_entry) } + mad = proc { print_it.call("mapped_entry", @mapped_entry) } + ext = proc { print_it.call("external_entry", @external_entry) } call_order = parsing? ? [ext, mad, int, fin] : [fin, int, mad, ext] - call_order.each {|proc| proc.call} + call_order.each(&:call) end end end end end