require 'yaml' require 'pp' require 'util' class Entity def initialize(name, dependent, columns = []) @name = name @dependent = dependent @columns = columns @foreignkeys = [] end def dependent? @dependent end def to_dot res = "" res << %Q!"#{@name}" [#{@dependent? 'shape=Mrecord, ' : 'shape=record, '}label=\"{#{@name}|! @foreignkeys.each{|f| res << "#{f}\\l"} @columns.each{|c| res << "#{c}\\l"} res << '}"]' end attr_accessor :name, :dependent, :columns, :foreignkeys end class Tables def initialize(table_node, label=nil, category=false) @label = label @category = category @table_node = table_node @entity_dict = {} table_node.each {|t| eval_entity(t) } end attr_accessor :entity_dict, :label, :table_node, :category def entity_dict_to_dot res = "" s = [] @entity_dict.each_value{|e| s << e } if @category res << "label=#{@label}\n" if @label res << "style=invis\n" unless @label end s.sort{|a, b| a.name <=> b.name}.each{|e| res << e.to_dot << "\n"} res end private def eval_entity(table) columns = table["columns"]; columns ||= [] @entity_dict[table["name"]] ||= Entity.new(table["name"], table["dependent"], columns) end end class Ymldot attr_accessor :file_name def initialize(filepath) open(filepath) do |f| str = f.read @node = YAML.load(str) end @file_name = $1 if filepath[/(\w+).yml\z/] @entity_dict = {} @category = [] @one_relations = [] @many_relations = [] eval_yml end def dot_generate code = "" code += <<"EOS" digraph #{@file_name} { #{add_2_tab(config_to_dot)} #{add_2_tab(entity_dict_to_dot)} #{add_2_tab(relations_to_dot)} } EOS end private def add_2_tab(str) res = "" str.each_line{|s| res << " " << s} res end def config_to_dot res = "" res << "graph[overlap=false, splines=true]\n" res << %Q!node [fontname="#{@config["font"]}"]! if @config["font"] res end def entity_dict_to_dot res = "" unless @category.empty? @category.each_with_index do |c, i| unless c.category res << c.entity_dict_to_dot else res << "subgraph cluster#{i} {\n" res << add_2_tab(c.entity_dict_to_dot) res << "}\n" end end end res end def relations_to_dot res = "" @one_relations.sort{|a, b| a[:self].name <=> b[:self].name}.each do |r| res << %Q!"#{r[:self].name}" -> "#{r[:have].name}" [arrowtail=none arrowhead=dot headlabel="1" taillabel="1"]! << "\n" end @many_relations.sort{|a, b| a[:self].name <=> b[:self].name}.each do |r| res << %Q!"#{r[:self].name}" -> "#{r[:have].name}" [arrowtail=none arrowhead=dot headlabel="n" taillabel="1"]! << "\n" end res end def eval_yml @config = @node["config"]? @node["config"] : {} @category << Tables.new(@node["tables"]) if @node["tables"] and !@node["tables"].empty? @node["category"].each{|c| @category << Tables.new(c["tables"], c["label"], true) } if @node["category"] @category.each{|c| c.entity_dict.each_pair{|k, v| @entity_dict[k] = v}} @category.each{|c| c.table_node.each{|e| eval_relation(e) } } end def eval_relation(table) foreignkeys = table["foreignkeys"] tname = table["name"] return unless foreignkeys eval_relation_has_many(foreignkeys["has_many"], tname) if foreignkeys["has_many"] eval_relation_has_one(foreignkeys["has_one"], tname) if foreignkeys["has_one"] eval_relation_hmabt(foreignkeys["has_many_and_belongs_to"], tname) if foreignkeys["has_many_and_belongs_to"] eval_relation_polymorphic(foreignkeys["polymorphic"], tname) if foreignkeys["polymorphic"] end def eval_relation_has_many(keys, tname) keys.each do |rel| @entity_dict[rel].foreignkeys << "#{singularize(tname)}ID(FK)" @many_relations << {:self => @entity_dict[tname], :have => @entity_dict[rel]} end end def eval_relation_has_one(keys, tname) keys.each do |rel| @entity_dict[rel].foreignkeys << "#{singularize(tname)}ID(FK)" @one_relations << {:self => @entity_dict[tname], :have => @entity_dict[rel]} end end def eval_relation_hmabt(keys, tname) keys.each do |rel| join_tname = "#{rel}_#{tname}" return if @entity_dict.has_key? join_tname join_tname = "#{tname}_#{rel}" @entity_dict[join_tname] ||= Entity.new(join_tname, true) @entity_dict[join_tname].foreignkeys << "#{singularize(tname)}ID(FK)" @entity_dict[join_tname].foreignkeys << "#{singularize(rel)}ID(FK)" # make new category keys = [] keys << "#{tname}ID(FK)" keys << "#{rel}ID(FK)" @category << Tables.new([{"name" => join_tname, "columns" => keys}]) @many_relations << {:self => @entity_dict[tname], :have => @entity_dict[join_tname]} @many_relations << {:self => @entity_dict[rel], :have => @entity_dict[join_tname]} end end def eval_relation_polymorphic(keys, tname) keys.each do |rel| @entity_dict[tname].foreignkeys << "#{singularize(rel["name"])}ID(FK)" if key = rel["name"] @entity_dict[tname].columns.unshift "#{singularize(rel["type"])}(type)" if key = rel["type"] rel["tables"].each do |t| @one_relations << {:self => @entity_dict[t], :have => @entity_dict[tname] } end end end end