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