lib/eco/api/common/people/person_parser.rb in eco-helpers-1.5.1 vs lib/eco/api/common/people/person_parser.rb in eco-helpers-1.5.2
- old
+ new
@@ -7,10 +7,14 @@
#
# @attr_reader schema [Ecoportal::API::V1::PersonSchema, nil] schema of person details that this parser will be based upon.
# @attr_reader details_attrs [Array<String>] internal names of schema details attributes.
# @attr_reader all_attrs [Array<String>] all the internal name attributes, including _core_, _account_ and _details_.
class PersonParser
+ extend Eco::API::Common::ClassAutoLoader
+ autoloads_children_of "Eco::API::Common::Loaders::Parser"
+ autoload_namespace_ignore "Eco::API"
+
CORE_ATTRS = ["id", "external_id", "email", "name", "supervisor_id", "filter_tags", "freemium"]
ACCOUNT_ATTRS = ["policy_group_ids", "default_tag", "send_invites", "landing_page_id", "login_provider_ids"]
TYPE = [:select, :text, :date, :number, :phone_number, :boolean, :multiple]
FORMAT = [:csv, :xml, :json]
@@ -41,16 +45,23 @@
@schema = Ecoportal::API::Internal::PersonSchema.new(JSON.parse(schema.doc.to_json))
@details_attrs = @schema&.fields.map { |fld| fld.alt_id }
end
@all_attrs = CORE_ATTRS + ACCOUNT_ATTRS + @details_attrs
+ self.class.autoload_children(self)
end
def patched!
@patch_version += 1
end
+ def new(schema: nil)
+ self.class.new(schema: schema || self.schema).merge(self)
+ end
+
+ # @!group Scopping attributes (identifying, presence & active)
+
# Scopes `source_attrs` using the _**core** attributes_.
# @note use this helper to know which among your attributes are **core** ones.
# @param source_attrs [Array<String>]
# @return [Array<String>] the scoped **core** attributes, if `source_attrs` is not `nil`. All the _core attributes_, otherwise.
def target_attrs_core(source_attrs = nil)
@@ -76,13 +87,13 @@
scoped_attrs(source_attrs, ACCOUNT_ATTRS)
end
# Lists all defined attributes, types and formats.
# @return [Array<String>] the list of defined parsers/serializers.
- #def list_defined
- # @parsers.keys
- #end
+ def defined_list
+ @parsers.keys
+ end
# Returns a list of all the internal attributes of the model that have a parser defined.
# @note
# - it excludes any parser that is not in the model, such as type parsers (i.e. `:boolean`, `:multiple`)
# - the list is sorted according `CORE_ATTRS` + `ACCOUNT_ATTRS` + schema attrs
@@ -92,14 +103,23 @@
defined = (all_attrs || defined) && defined
defined - (defined - all_attrs)
end
# Returns a list of all the internal attributes of the model that have a parser defined & that should be active.
- # @param [Hash, Array<String>]
- # @return [Array<String>] list of all attribute defined parsers that should be active.
- def active_attrs(source_data)
- defined_attrs.select {|attr| @parsers[attr].parser_active?(source_data)}
+ # @param source_data [Hash, Array<String>] the data that we scope for parsing
+ # @param phase [Symbol] the phase when the attr parser is expected to be called.
+ # Can be [:internal, :final, :person]
+ # @param process [Symbol] either `:parse` or `:serialize`, depending if we want to parse or serialize the `attr`.
+ # @return [Array<String>] list of all attribute defined parsers that should be active for the given `source_data`.
+ def active_attrs(source_data, phase = :any, process: :parse)
+ defined_attrs.select do |attr|
+ if process == :serialize
+ @parsers[attr].serializer_active?(phase)
+ else
+ @parsers[attr].parser_active?(source_data, phase)
+ end
+ end
end
# Returns a list of all the internal attributes of the model that do **not** have a parser defined.
# @note it excludes any parser that is **not** in the model, such as type parsers (i.e. :boolean, :multiple)
# @return [Array<String>] list of all attributes without a defined parser.
@@ -110,84 +130,97 @@
# @param attr [String] internal name of an attribute.
# @return [Boolean] `true` if the attribute `attr` has parser defined, and `false` otherwise.
def defined?(attr)
@parsers.key?(attr)
end
+ # @!endgroup
+ # @!group Defining attributes
+
# Helper to **merge** a set of parsers of another `PersonParser` into the current object.
# @note if there are parsers with same name, it **overrides** the ones of the current object with them.
# @param parser [Eco::API::Common::People::PersonParser] a `PersonParser` containing defined parsers.
# @return [Eco::API::Common::People::PersonParser] the current object (to ease chainig).
def merge(parser)
return self if !parser
raise "Expected a PersonParser object. Given #{parser}" if !parser.is_a?(PersonParser)
- @parsers.merge!(parser.hash)
+ to_h.merge!(parser.to_h)
patched!
self
end
-
# Helper to define and associate a parser/serializer to a type or attribute.
# @raise [Exception] if trying to define a parser/serializer for:
# - an unkown attribute (`String`)
# - an unrecognized type or format (`Symbol`)
# @param attr [String] type (`Symbol`) or attribute (`String`) to define the parser/serializer to.
# @param dependencies [Hash] dependencies to be used when calling the parser/serializer.
# @yield [parser] the definition of the parser.
# @yieldparam parser [Eco::Language::Models::ParserSerializer] parser to define.
# @return [Eco::API::Common::People::PersonParser] the current object (to ease chainig).
def define_attribute(attr, dependencies: {}, &definition)
- if !valid?(attr)
+ unless valid?(attr)
msg = "The attribute '#{attr_to_str(attr)}' is not part of core, account or target schema, or does not match any type: #{@details_attrs}"
raise msg
end
Eco::API::Common::People::PersonAttributeParser.new(attr, dependencies: dependencies).tap do |parser|
@parsers[attr] = parser
definition.call(parser)
end
patched!
self
end
+ # @!endgroup
+ # @!group Launching parser/serializer
+
# Call to parser `source` value of attribute or type `attr` into an internal valid value.
# @note dependencies introduced on `parse` call will be merged with those defined during the
# initialization of the parser `attr`.
# @raise [Exception] if there is **no** parser for attribute or type `attr`.
# @param attr [String] target attribute or type to **parse**.
# @param source [Any] source value to be parsed.
+ # @param phase [Symbol] the phase when the attr parser is expected to be called.
+ # Must be [:internal, :final]
# @param deps [Hash] key-value pairs of call dependencies.
# @return [Any] a valid internal value.
- def parse(attr, source, deps: {})
+ def parse(attr, source, phase = :internal, deps: {})
raise "There is no parser for attribute '#{attr}'" if !self.defined?(attr)
- @parsers[attr].parse(source, dependencies: deps)
+ @parsers[attr].parse(source, phase, dependencies: deps)
end
# Call to serialise `object` value of attribute or type `attr` into an external valid value.
# @note dependencies introduced on `serialise` call will be merged with those defined during the
# initialization of the parser/serialiser `attr`.
# @raise [Exception] if there is **no** serialiser for attribute or type `attr`.
# @param attr [String] target attribute or type to **serialize**.
# @param object [Any] object value to be serialized.
+ # @param phase [Symbol] the phase when the attr serializer is expected to be called.
+ # Must be [:internal, :final, :person]
# @param deps [Hash] key-value pairs of call dependencies.
# @return a valid external value.
- def serialize(attr, object, deps: {})
+ def serialize(attr, object, phase = :person, deps: {})
raise "There is no parser for attribute '#{attr}'" if !self.defined?(attr)
- @parsers[attr].serialize(object, dependencies: deps)
+ @parsers[attr].serialize(object, phase, dependencies: deps)
end
+ # @!endgroup
protected
# @return [Hash] attr-parser pairs with all the defined type and attribute parsers/serializers.
- def hash
+ def to_h
+ self.class.autoload_children(self)
@parsers
end
private
+ # Some attribute parsers are **inactive**, and therefore out of scope
+ # @param source_attrs [Array<String>] the attrs we want to filter from to only those in scope.
+ # @param section_attrs [Array<String>] the attrs of reference to scope `source_attrs`.
+ # @return [Array<String>] the `source_attrs` in `section_attrs` + the `section_attrs` with active parser
def scoped_attrs(source_attrs, section_attrs)
- direct_attrs = source_attrs & section_attrs
- parsed_attrs = active_attrs(source_attrs) & section_attrs
- (source_attrs + parsed_attrs) & (direct_attrs + parsed_attrs)
+ (source_attrs | active_attrs(source_attrs)) & section_attrs
end
def attr_to_str(attr)
attr.is_a?(Symbol)? ":#{attr.to_s}" : "#{attr.to_s}"
end