module Eco::API::Common::People::SupervisorHelpers::ClassMethods
Public Instance Methods
identify_cyclic_chains(values)
click to toggle source
Identifies all the cyclic supervisor chains @note as `supervisors_tree` will have any entry involved in a cycle at the top, it just checks all the top entries against their offspring @return [Array<Array>] the sets of entries that are cyclic
# File lib/eco/api/common/people/supervisor_helpers.rb, line 39 def identify_cyclic_chains(values) raise "Expected non hash Enumerable. Given: #{values.class}" if values.is_a?(Hash) return [] unless values && values.is_a?(Enumerable) identify = Proc.new do |top_sup, offspring, chain = [top_sup]| next [] if offspring.empty? offspring.each_with_object([]) do |(sup, subordinates), set| break set unless set.empty? if top_sup.supervisor_id == sup.id set.concat(chain, [sup]) else set = identify.call(top_sup, subordinates, chain | [sup]) end end end supervisors_tree(values).each_with_object([]) do |(top_sup, offspring), sets| if (set = identify.call(top_sup, offspring)) && !set.empty? sets.push(set) end end end
print_tree(tree, lev: 0)
click to toggle source
# File lib/eco/api/common/people/supervisor_helpers.rb, line 72 def print_tree(tree, lev: 0) puts tree_to_str(tree) end
sort_by_supervisors(values, supervisors_first: true)
click to toggle source
Reorders as follows:
1. supervisors, people with no supervisor or where their supervisor not present 2. subordinates
@return [Array<PersonEntry>] `values` sorted by supervisors/subordinates
# File lib/eco/api/common/people/supervisor_helpers.rb, line 19 def sort_by_supervisors(values, supervisors_first: true) raise "Expected non hash Enumerable. Given: #{values.class}" if values.is_a?(Hash) return [] unless values && values.is_a?(Enumerable) roam = Proc.new do |tree| [].tap do |out| sub_outs = tree.empty?? [] : tree.map {|sup, subtree| roam.call(subtree)} tree.each do |sup, subtree| sout = subtree.empty?? [] :roam.call(subtree) supervisors_first ? sout.unshift(sup) : sout.push(sup) out.concat(sout) end end end roam.call(supervisors_tree(values)) end
supervisors_tree(values)
click to toggle source
Generates a `Hash` tree structure, where:
* **keys** are nodes * **values** are `Hash` subtree structures of `key` subordinates
@note it is resilient to cyclic supervisors (it will just add the last at the top) @param values [Enumerable<Object>] of objects with methods:
`id`, `external_id` and `supervisor_id`
@return [Hash] the tree structure
# File lib/eco/api/common/people/supervisor_helpers.rb, line 83 def supervisors_tree(values) raise "Expected non hash Enumerable. Given: #{values.class}" if values.is_a?(Hash) return {} unless values && values.is_a?(Enumerable) idx = get_super_indexes(values) processed = [] subtree = Proc.new do |entry, level, toptree| if processed.include?(entry) next {} unless toptree.key?(entry) && level > 0 # needs to be moved as a child subnodes = toptree.delete(entry) processed.delete(entry) end subnodes ||= {}.tap do |tree| subs = idx[:subordinates].call(entry) processed.push(entry) next nil unless subs && !subs.empty? subs.each do |sub| sub_tree = subtree.call(sub, level + 1, toptree) tree.merge!(sub_tree) end end {entry => subnodes} end {}.tap do |tree| idx[:by_sup].keys.each do |sup_id| if sup = idx[:supers][sup_id] tree.merge!(subtree.call(sup, 0, tree)) else idx[:by_sup][sup_id].each do |sub| tree.merge!(subtree.call(sub, 0, tree)) end end end end end
tree_to_str(tree, lev: 0)
click to toggle source
# File lib/eco/api/common/people/supervisor_helpers.rb, line 62 def tree_to_str(tree, lev: 0) raise "Required Hash tree structure. Given: #{tree.class}" unless tree.is_a?(Hash) "".tap do |str| tree.each do |entry, subtree| str << "#{" " * lev}+-- #{entry.id || entry.external_id}\n" str << tree_to_str(subtree, lev: lev + 1) unless !subtree || subtree.empty? end end end
Private Instance Methods
get_super_indexes(values)
click to toggle source
# File lib/eco/api/common/people/supervisor_helpers.rb, line 125 def get_super_indexes(values) raise "Expected non hash Enumerable. Given: #{values.class}" if values.is_a?(Hash) {}.tap do |indexes| indexes[:by_id] = values.map do |e| e.id && [e.id, e] end.compact.to_h indexes[:by_ext] = values.map do |e| e.external_id && [e.external_id, e] end.compact.to_h indexes[:by_sup] = {}.tap do |by_s| values.group_by do |e| e.supervisor_id end.tap do |by_sup| by_s[nil] = by_sup.delete(nil) if by_sup.key?(nil) by_s.merge!(by_sup) end end indexes[:supers] = {}.tap do |sups| indexes[:by_ext].select do |ext, e| ext && indexes[:by_sup].key?(ext) end.tap {|found| sups.merge!(found)} indexes[:by_id].select do |id, e| id && indexes[:by_sup].key?(id) end.tap {|found| sups.merge!(found)} end indexes[:is_super] = Proc.new do |entry| !!(indexes[:supers][entry.id] || indexes[:supers][entry.external_id]) end indexes[:subordinates] = Proc.new do |entry| subs = nil if sup = indexes[:supers][entry.id] || indexes[:supers][entry.external_id] subs ||= indexes[:by_sup][sup.id] unless !sup.id subs ||= indexes[:by_sup][sup.external_id] unless !sup.external_id end subs end end end