module Metasploit::Credential::EntityRelationshipDiagram
@todo Extract (along with MetasploitDataModel::EntityRelationshipDiagram), common ERD code and move to metasploit-documentation or metasploit-entity_relationship_diagram
Constants
- ATTRIBUTES
Enable all attributes
- DEFAULT_OPTIONS
Default options for Diagram.
- INDIRECT
Only show direct relationships since the ERD is for use with SQL and there is no need to show has_many :through for those purposes.
- INHERITANCE
Show inheritance for Single-Table Inheritance
- NOTATION
Use crowsfoot notation since its what we use for manually drawn diagrams.
Public Class Methods
Cluster of classes that are reachable through belongs_to from `classes`.
@param classes [Array<Class<ActiveRecord::Base>>] classes that must be in cluster. All other classes in the
returned cluster will be classes to which `classes` belong directly or indirectly.
@return [Set<Class<ActiveRecord::Base>>]
# File lib/metasploit/credential/entity_relationship_diagram.rb, line 61 def self.cluster(*classes) class_queue = classes.dup visited_class_set = Set.new until class_queue.empty? klass = class_queue.pop # add immediately to visited set in case there are recursive associations visited_class_set.add klass # only iterate belongs_to as they need to be included so that foreign keys aren't let dangling in the ERD. reflections = klass.reflect_on_all_associations(:belongs_to) reflections.each do |reflection| if reflection.options[:polymorphic] target_klasses = polymorphic_classes(reflection) else target_klasses = [reflection.klass] end target_klasses.each do |target_klass| unless visited_class_set.include? target_klass class_queue << target_klass end end end end visited_class_set end
All {cluster clusters} of classes that are reachable through belongs_to from each ActiveRecord::Base descendant
@return [Hash{Class<ActiveRecord::Base> => Set<Class<ActiveRecord::Base>>}] Maps entry point to cluster to its
cluster.
# File lib/metasploit/credential/entity_relationship_diagram.rb, line 43 def self.cluster_by_class cluster_by_class = {} Metasploit::Credential::Engine.instance.eager_load! ActiveRecord::Base.descendants.each do |klass| klass_cluster = cluster(klass) cluster_by_class[klass] = klass_cluster end cluster_by_class end
Creates Graphviz diagram.
@param options [Hash{Symbol => Object}] @option options [RailsERD::Domain] :domain ({domain}) The domain to diagram. @option options [String] :filename name of file (without extension) to which to write diagram. @option options [String] :title Title of the diagram to include on the diagram. @return [String] path where diagram was written.
# File lib/metasploit/credential/entity_relationship_diagram.rb, line 98 def self.create(options={}) domain = options[:domain] domain ||= self.domain diagram_options = options.except(:domain) merged_diagram_options = DEFAULT_OPTIONS.merge(diagram_options) require 'rails_erd/domain' diagram = RailsERD::Diagram::Graphviz.new(domain, merged_diagram_options) path = diagram.create path end
Domain containing all models in this gem.
@return [RailsERD::Domain]
# File lib/metasploit/credential/entity_relationship_diagram.rb, line 116 def self.domain require_models require 'rails_erd/domain' RailsERD::Domain.generate end
Set of largest clusters from {cluster_by_class}.
@return [Array<Set<Class<ActiveRecord::Base>>>]
# File lib/metasploit/credential/entity_relationship_diagram.rb, line 126 def self.maximal_clusters clusters = cluster_by_class.values unique_clusters = clusters.uniq maximal_clusters = unique_clusters.dup cluster_queue = unique_clusters.dup until cluster_queue.empty? cluster = cluster_queue.pop proper_subset = false maximal_clusters.each do |maximal_cluster| if cluster.proper_subset? maximal_cluster proper_subset = true break end end if proper_subset maximal_clusters.delete(cluster) end end maximal_clusters end
Calculates the target classes for a polymorphic `belongs_to`.
@return [Array<ActiveRecord::Base>]
# File lib/metasploit/credential/entity_relationship_diagram.rb, line 156 def self.polymorphic_classes(belongs_to_reflection) name = belongs_to_reflection.name ActiveRecord::Base.descendants.each_with_object([]) { |descendant, target_classes| has_many_reflections = descendant.reflect_on_all_associations(:has_many) has_many_reflections.each do |has_many_reflection| as = has_many_reflection.options[:as] if as == name target_classes << descendant end end } end