require 'set' require 'locabulary' require 'locabulary/exceptions' require 'locabulary/item' require 'locabulary/facet_wrapper_for_item' module Locabulary module Commands # Responsible for building a hierarchical tree from faceted items, and ordering the nodes as per the presentation sequence for the # associated predicate_name. class BuildOrderedHierarchicalTreeCommand # @api private # @since 0.5.0 # # @param options [Hash] # @option options [String] :predicate_name # @option options [Array<#hits, #value>] :faceted_items # @option options [String] :faceted_item_hierarchy_delimiter # For any given item in faceted_items how is the hierarchy encoded in the :value? # # @return [Array<FacetWrapperForItem>] def self.call(options = {}) new(options).call end def initialize(options = {}) @predicate_name = options.fetch(:predicate_name) @faceted_items = options.fetch(:faceted_items) @faceted_item_hierarchy_delimiter = options.fetch(:faceted_item_hierarchy_delimiter) @locabulary_item_class = Item.class_to_instantiate(predicate_name: predicate_name) end def call items = [] hierarchy_graph_keys = {} top_level_slugs = Set.new faceted_items.each do |faceted_item| item = build_item(faceted_item) items << item top_level_slugs << item.root_slug hierarchy_graph_keys[item.term_label] = item end associate_parents_and_childrens_for(hierarchy_graph_keys, items) top_level_slugs.map { |slug| hierarchy_graph_keys.fetch(slug) }.sort end private attr_reader :locabulary_item_class, :predicate_name, :faceted_items, :faceted_item_hierarchy_delimiter def associate_parents_and_childrens_for(hierarchy_graph_keys, items) items.each do |item| begin hierarchy_graph_keys.fetch(item.parent_term_label).add_child(item) unless item.parent_slugs.empty? rescue KeyError => error raise Exceptions::MissingHierarchicalParentError.new(predicate_name, error) end end end def build_item(faceted_node) term_label = convert_faceted_node_value_to_term_label(faceted_node.value) locabulary_item = find_locabulary_item(predicate_name: predicate_name, term_label: term_label) if locabulary_item FacetWrapperForItem.build_for_faceted_node_and_locabulary_item(faceted_node: faceted_node, locabulary_item: locabulary_item) else FacetWrapperForItem.build_for_faceted_node(faceted_node: faceted_node, predicate_name: predicate_name, term_label: term_label) end end def find_locabulary_item(*args) Locabulary.item_for(*args) rescue Exceptions::ItemNotFoundError, Exceptions::MissingPredicateNameError # Either the predicate name didn't exist or the item didn't exist for the given predicate name # Given that we are building from an alternate source, this is an acceptable error. It is possible # that we want to consider a developer notification but not abort the whole process. nil end # Responsible for converting the hierarchy delimiter of the facet item to the hierarchy delimiter of the Locabulary::Item. def convert_faceted_node_value_to_term_label(value) value.gsub( /(?<!#{faceted_item_hierarchy_delimiter})#{faceted_item_hierarchy_delimiter}(?!#{faceted_item_hierarchy_delimiter})/, locabulary_item_class.hierarchy_delimiter ) end end end end