class Hierarchy
Public Class Methods
associations(klass)
click to toggle source
Return the full hierarchy starting from the provided class
# File lib/hierarchy_tree.rb, line 6 def self.associations(klass) build_hierarchy(klass) end
descendants(klass)
click to toggle source
Return just the descendant classes of a provided class
# File lib/hierarchy_tree.rb, line 11 def self.descendants(klass) @descendants = [] build_descendants(klass) @descendants end
loop?(klass)
click to toggle source
# File lib/hierarchy_tree.rb, line 17 def self.loop?(klass) @cache = {} false if dfs_hierarchy(class: klass, classes?: false) rescue SystemStackError true end
Private Class Methods
build_descendants(klass)
click to toggle source
# File lib/hierarchy_tree.rb, line 81 def self.build_descendants(klass) dfs_descendants(class: klass, classes?: true) rescue SystemStackError Rails.logger.ap "Infinite loop detected and handled for #{opts[:class]} descendants", :warn [] end
build_hierarchy(klass)
click to toggle source
# File lib/hierarchy_tree.rb, line 26 def self.build_hierarchy(klass) @cache = {} dfs_hierarchy(class: klass) rescue SystemStackError Rails.logger.ap "Infinite loop detected and handled for #{opts[:class]} hierarchy", :warn [] end
children_classes(opts)
click to toggle source
# File lib/hierarchy_tree.rb, line 57 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
dfs_descendants(opts, klass_name = nil)
click to toggle source
# File lib/hierarchy_tree.rb, line 88 def self.dfs_descendants(opts, klass_name = nil) return if klass_name.in? @descendants @descendants.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 = [])
click to toggle source
# File lib/hierarchy_tree.rb, line 34 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)
click to toggle source
# File lib/hierarchy_tree.rb, line 75 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)
click to toggle source
# File lib/hierarchy_tree.rb, line 52 def self.leaf?(klass) return true if walkables(klass).empty? false end
walkables(klass)
click to toggle source
# File lib/hierarchy_tree.rb, line 68 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