Class: Hierarchy

Inherits:
Object
  • Object
show all
Defined in:
lib/hierarchy_tree.rb

Overview

Debug ################ rm hierarchy-tree-X.Y.Z.gem gem build hierarchy_tree gem install hierarchy-tree-X.Y.Z.gem ruby -Itest test/test_hierarchy_tree.rb

Class Method Summary collapse

Class Method Details

.associations(klass) ⇒ Object

Return the full hierarchy starting from the provided class



12
13
14
# File 'lib/hierarchy_tree.rb', line 12

def self.associations(klass)
  build_hierarchy(class: klass)
end

.build_descendants(klass) ⇒ Object



92
93
94
95
96
97
# File 'lib/hierarchy_tree.rb', line 92

def self.build_descendants(klass)
  dfs_descendants(class: klass, classes?: true)
rescue SystemStackError
  Rails.logger.ap "Infinite loop detected and handled for #{opts[:class]} classes_list", :warn
  []
end

.build_hierarchy(opts) ⇒ Object



37
38
39
40
41
42
43
# File 'lib/hierarchy_tree.rb', line 37

def self.build_hierarchy(opts)
  @cache = {}
  dfs_hierarchy(opts)
rescue SystemStackError
  Rails.logger.ap "Infinite loop detected and handled for #{opts[:class]} hierarchy", :warn
  []
end

.children_classes(opts) ⇒ Object



68
69
70
71
72
73
74
75
76
77
# File 'lib/hierarchy_tree.rb', line 68

def self.children_classes(opts)
  walkables(opts[:class]).map do |reflection|
    child_class = get_class(reflection)
    if opts[:classes?]
      [child_class, child_class.to_s]
    else
      [child_class, reflection.name]
    end
  end.uniq
end

.classes(klass) ⇒ Object

Return the full hierarchy starting from the provided class



17
18
19
# File 'lib/hierarchy_tree.rb', line 17

def self.classes(klass)
  build_hierarchy(class: klass, classes?: true)
end

.classes_list(klass) ⇒ Object

Return an array o



22
23
24
25
26
# File 'lib/hierarchy_tree.rb', line 22

def self.classes_list(klass)
  @classes_list = []
  build_descendants(klass)
  @classes_list
end

.dfs_descendants(opts, klass_name = nil) ⇒ Object



99
100
101
102
103
104
105
106
107
# File 'lib/hierarchy_tree.rb', line 99

def self.dfs_descendants(opts, klass_name = nil)
  return if klass_name.in? @classes_list
  @classes_list.push(klass_name) if klass_name.present?
  children_classes(opts).each do |child_klass, child_name|
    child_opts = { class: child_klass, classes?: opts[:classes?] }
    dfs_descendants(child_opts, child_name)
  end
  true
end

.dfs_hierarchy(opts, klass_name = nil, ancestral_nodes = []) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/hierarchy_tree.rb', line 45

def self.dfs_hierarchy(opts, klass_name = nil, ancestral_nodes = [])
  return @cache[klass_name] if klass_name.in? @cache.keys
  return klass_name if opts[:class].in? ancestral_nodes # Early abort to not enter in a cycle
  if leaf?(opts[:class])
    @cache[klass_name] = klass_name
    return klass_name if klass_name.present? # Leaf
    [] # Leaf and Root
  else
    ancestral_nodes.push(opts[:class])
    children_hierarchies = children_classes(opts).map do |c_class, c_name|
      dfs_hierarchy({ class: c_class, classes?: opts[:classes?] }, c_name, ancestral_nodes.dup)
    end
    @cache[klass_name] = { klass_name => children_hierarchies }
    return @cache[klass_name] if klass_name.present? # Middle
    children_hierarchies # Root
  end
end

.get_class(reflection) ⇒ Object



86
87
88
89
90
# File 'lib/hierarchy_tree.rb', line 86

def self.get_class(reflection)
  child = reflection.name.to_s.singularize.classify
  child = reflection.options[:class_name].to_s if reflection.options.key?(:class_name)
  child.constantize
end

.leaf?(klass) ⇒ Boolean

Returns:

  • (Boolean)


63
64
65
66
# File 'lib/hierarchy_tree.rb', line 63

def self.leaf?(klass)
  return true if walkables(klass).empty?
  false
end

.loop?(klass) ⇒ Boolean

Returns:

  • (Boolean)


28
29
30
31
32
33
# File 'lib/hierarchy_tree.rb', line 28

def self.loop?(klass)
  @cache = {}
  false if dfs_hierarchy(class: klass, classes?: false)
rescue SystemStackError
  true
end

.walkables(klass) ⇒ Object



79
80
81
82
83
84
# File 'lib/hierarchy_tree.rb', line 79

def self.walkables(klass)
  # get all models associated with :has_many or :has_one that are walkable.
  klass.reflections.values.select do |r|
    r.macro.in? %i[has_one has_many] and not r.options.key?(:through)
  end
end