class PIM::Services::ItemService::ItemHierarchy

Public Class Methods

new(*args) click to toggle source
Calls superclass method PIM::Utils::EnhancedStruct::new
# File services.rb, line 2799
def initialize *args
  super

  # Ensure root_primary_key is set and is a string
  raise "'root_primary_key' must not be empty" if PIM::Utils.is_empty?(self.root_primary_key)
  self.root_primary_key = self.root_primary_key.to_s

  # Ensure name is set and is a string
  raise "'name' must not be empty" if PIM::Utils.is_empty?(self.name)
  self.name = self.name.to_s

  # Create an internal deep copy of 'hierarchy',
  # so it is not available outside of this class
  @_hierarchy = to_internal_hierarchy(self.hierarchy)

  # Overwrite methods to hide internal hierarchy
  class << self

    define_method(:hierarchy) {
      return @_hierarchy.map { |k,v| [k.to_s, v.to_a] }.to_h
    }

    define_method(:'hierarchy=') { |hierarchy|
      @hierarchy = to_internal_hierarchy(hierarchy)
    }

  end

end

Public Instance Methods

add_children(parent, *children, validate: true) click to toggle source

Adds the specified items as ‘child’ items of the specified ‘parent’. Returns a copy(!) of the Set of all children attached to the parent.

# File services.rb, line 3031
def add_children parent, *children, validate: true

  children = PIM::Utils.stringified_non_empty_set(children)

  parent = parent.to_s
  validate_parents(parent) if validate

  existing_children = @_hierarchy[parent]
  if existing_children.nil?

    # Create a new set
    validate_children(parent, children) if validate
    @_hierarchy[parent] = existing_children = Set.new(children)

  else

    # Only retain children which do not yet exist, to minimize validation
    children -= existing_children
    validate_children(parent, children) if validate

    # Add children to existing children
    existing_children.merge(children)

  end

  return existing_children.to_a
end
as_json() click to toggle source
# File services.rb, line 2835
def as_json

  # Convert values back to "camelized" hash,
  # but leave out the non writable fields
  PIM::Utils.unsymbolized_camelized_hash({
    root_primary_key: self.root_primary_key,
    name: self.name,
    checksum: self.checksum,
    hierarchy: hierarchy
  })

end
get_children(parent) click to toggle source

Returns a copy(!) of the Set of all children attached to the parent. Returns nil if no children are linked.

# File services.rb, line 2985
def get_children parent

  parent = parent.to_s
  children = @_hierarchy[parent]
  return nil if children.nil? or children.empty?

  return children.to_a
end
get_descendants(parent, include_self: false) click to toggle source

Returns a Set of all descendant items of the specified parent. Set will include the parent itself, if ‘include_self’ is set to true. Returns nil if parent does not exist in hierarchy.

# File services.rb, line 2998
def get_descendants parent, include_self: false

  parent = parent.to_s
  return nil if not @_hierarchy.include?(parent)

  descendants = Set.new
  descendants << parent if include_self

  _add_descendants @_hierarchy, @_hierarchy[parent], descendants: descendants

  return descendants
end
remove_children(parent, *children, validate: true) click to toggle source

Removes the specified items as ‘child’ items from the specified ‘parent’. Returns either a copy(!) of the Set of all children still attached to the parent, or nil, if the parent no longer has any children attached.

# File services.rb, line 3086
def remove_children parent, *children, validate: true

  children = PIM::Utils.stringified_non_empty_set(children)

  parent = parent.to_s
  existing_children = @_hierarchy[parent]
  return nil if existing_children.nil?

  # Only retain children which actually exist, and ensure removed children are still valid parents
  children &= existing_children
  validate_parents(parent, children) if validate

  # Remove the whole parent if all children are to be removed
  if children == existing_children
    @_hierarchy.delete(parent)
    return nil
  end

  # Remove children
  existing_children.subtract(children)

  return existing_children.to_a
end
remove_parent(parent, validate: true) click to toggle source

Removes all child items from the specified ‘parent’ item. Returns a copy(!) of the Set of all children previously attached to the parent, or nil, if parent had no children attached before.

# File services.rb, line 3114
def remove_parent parent, validate: true

  parent = parent.to_s
  existing_children = @_hierarchy[parent]
  return nil if existing_children.nil?

  # Ensure removed children are still valid parents
  validate_parents(parent, existing_children) if validate

  # Remove the whole parent
  @_hierarchy.delete(parent)

  return existing_children.to_a
end
set_children(parent, *children, validate: true) click to toggle source

Sets the specified items as ‘child’ items of the specified ‘parent’. Returns a copy(!) of the Set of all children attached to the parent. Returns nil if an empty list of children was specified.

# File services.rb, line 3063
def set_children parent, *children, validate: true

  children = PIM::Utils.stringified_non_empty_set(children)

  # If no children are specified, remove the parent
  if children.empty?
    remove_parent(parent, validate: validate)
    return nil
  end

  parent = parent.to_s
  validate_parents(parent) if validate

  validate_children(parent, children) if validate
  @_hierarchy[parent] = existing_children = Set.new(children)

  return existing_children.to_a
end
validate_children(parent = nil, children = nil, hierarchy: @_hierarchy, root: self.root_primary_key, raise_on_error: true) click to toggle source

Ensure that ‘children’ under ‘parent’ are valid children, i.e. they and their descendants do not form a cycle.

If ‘children’ is not specified, only ensure that children to ‘parent’ are valid, i.e. they and their descendants do not form a cycle.

If neither ‘parent’ nor ‘children’ are specified, validate all children of the hierarchy.

If ‘raise_on_error’ is true, raises an exception if not valid, otherwise returns a list of invalid paths

# File services.rb, line 2924
def validate_children parent = nil,
                      children = nil,
                      hierarchy: @_hierarchy,
                      root: self.root_primary_key,
                      raise_on_error: true

  if parent.nil?

    # Start at root and check children of 'root'
    parent_path = [root]
    check_children = hierarchy[root]

  elsif children.nil?

    # Start at parent and check children of it
    parent_path = [parent]
    check_children = PIM::Utils.flattened_compacted_array(hierarchy[parent])

  else

    # Start at parent and only check specified children
    parent_path = [parent]
    check_children = PIM::Utils.flattened_compacted_array(children)

  end
  return true if check_children.empty?

  cycle_paths = Set.new

  _check_cycle(hierarchy, parent_path, check_children, cycle_paths: cycle_paths, raise_on_error: raise_on_error)

  return cycle_paths.empty? ? true : cycle_paths.to_a
end
validate_hierarchy(hierarchy: @_hierarchy, root: self.root_primary_key) click to toggle source

Validates the complete hierarchy and raises an exception if not valid.

# File services.rb, line 2850
def validate_hierarchy hierarchy: @_hierarchy, root: self.root_primary_key

  # Validate parent to exist as child
  validate_parents hierarchy: hierarchy, root: root

  # Validate child items to be cycle-free
  validate_children hierarchy: hierarchy, root: root

  return true
end
validate_parents(parent = nil, children = nil, hierarchy: @_hierarchy, root: self.root_primary_key, raise_on_error: true) click to toggle source

Ensure that ‘children’ under ‘parent’ are valid parents, i.e. they must be children to at least one other parent than ‘parent’.

If ‘children’ is not specified, only ensure that ‘parent’ is a valid parent, i.e. it is included in the hierarchy keys.

If neither ‘parent’ nor ‘children’ are specified, validate all parents of the hierarchy.

If ‘raise_on_error’ is true, raises an exception if not valid, otherwise returns a list of invalid parents

# File services.rb, line 2871
def validate_parents parent = nil,
                     children = nil,
                     hierarchy: @_hierarchy,
                     root: self.root_primary_key,
                     raise_on_error: true

  if parent.nil?

    # Check all keys
    check_parents = hierarchy.keys

  elsif children.nil?

    # Only check specified parent
    check_parents = PIM::Utils.flattened_compacted_array(parent)

  else

    # Only check children which are included in keys
    check_parents = hierarchy.keys & PIM::Utils.flattened_compacted_array(children)

  end
  return true if check_parents.empty?

  invalid_parents = Set.new

  # Ensure, each parent is a child to at least one other parent
  check_parents.each do |check_parent|

    # OK, if parent is root
    next if check_parent == root

    # OK, if parent exists in any of the hierarchy's value, but not for the ignored parent
    next if hierarchy.any? { |k, v| (parent.nil? or parent != k) and v.include?(check_parent) }

    raise "Parent '#{check_parent}' is not linked by any other parent in the hierarchy" if raise_on_error
    invalid_parents << check_parent

  end

  return invalid_parents.empty? ? true : invalid_parents.to_a
end