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 def to_csv res = "" res << "#{@name}\n" @foreignkeys.each{|f| res << "#{f}\n"} @columns.each do |c| if c.include? ":" res << "#{c.split(':').join(',')}\n" else res << "#{c}\n" end end 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 = {} @natural_order_entity = [] table_node.each {|t| eval_entity(t) } end attr_accessor :entity_dict, :label, :table_node, :category def to_dot res = "" s = @natural_order_entity if @category res << "label=#{@label}\n" if @label res << "style=invis\n" unless @label end s.each{|e| res << e.to_dot << "\n"} res end def to_csv res = "" @natural_order_entity.each{|e| res << e.to_csv << "\n"} res end def add_entity(table) eval_entity(table) end private def eval_entity(table) columns = table["columns"]; columns ||= [] unless @entity_dict[table["name"]] e = Entity.new(table["name"], table["dependent"], columns) @entity_dict[table["name"]] = e @natural_order_entity << e end 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 to_dot code = "" code += <<"EOS" digraph #{@file_name} { #{add_2_tab(config_to_dot)} #{add_2_tab(entity_to_dot)} #{add_2_tab(relations_to_dot)} } EOS end def to_csv csv = "" @category.each do |e| csv << e.to_csv end csv 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" if @config["size"] && @config["size"]["x"] && @config["size"]["y"] res << %Q!graph [size="#{(@config["size"]["x"]/2.54).round},#{(@config["size"]["y"]/2.54).round}"]\n! end raise "Error: please set 'font'" if @config["ja"] && @config["font"] if @config["font"] || @config["fontsize"] res << "node [" res << %Q!fontname="#{@config["font"]}"! if @config["font"] res << %Q!, fontsize=#{@config["fontsize"]}! if @config["fontsize"] res << "]" end res end def entity_to_dot res = "" unless @category.empty? @category.each_with_index do |c, i| unless c.category res << c.to_dot else res << "subgraph cluster#{i} {\n" res << add_2_tab(c.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, c) } } end def eval_relation(table, category) 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_habtm(foreignkeys["has_and_belongs_to_many"], tname, category) if foreignkeys["has_and_belongs_to_many"] eval_relation_polymorphic(foreignkeys["polymorphic"], tname) if foreignkeys["polymorphic"] end def eval_relation_has_many(keys, tname) keys.each do |rel| raise "Error Relation! has many #{tname} to #{rel}" if @entity_dict[rel] == nil @entity_dict[rel].foreignkeys << append_fk_str(singularize(tname)) @many_relations << {:self => @entity_dict[tname], :have => @entity_dict[rel]} end end def eval_relation_has_one(keys, tname) keys.each do |rel| raise "Error Relation! has one #{tname} to #{rel}" if @entity_dict[rel] == nil @entity_dict[rel].foreignkeys << append_fk_str(singularize(tname)) @one_relations << {:self => @entity_dict[tname], :have => @entity_dict[rel]} end end def eval_relation_habtm(keys, tname, category) keys.each do |rel| join_tname = [rel, tname].sort.join("_") return if category.entity_dict.has_key? join_tname # make new category keys = [] keys << append_fk_str(tname) keys << append_fk_str(rel) category.add_entity("name" => join_tname) category.entity_dict[join_tname].foreignkeys << append_fk_str(singularize(tname)) category.entity_dict[join_tname].foreignkeys << append_fk_str(singularize(rel)) @many_relations << {:self => category.entity_dict[tname], :have => category.entity_dict[join_tname]} @many_relations << {:self => category.entity_dict[rel], :have => category.entity_dict[join_tname]} end end def eval_relation_polymorphic(keys, tname) keys.each do |rel| @entity_dict[tname].foreignkeys << append_fk_str(singularize(rel["name"])) if key = rel["name"] @entity_dict[tname].columns.unshift "#{singularize(rel["type"])}(type)" if key = rel["type"] rel["tables"].each do |t| @many_relations << {:self => @entity_dict[t], :have => @entity_dict[tname] } end end end def append_fk_str(key) @config["lang"] ||= "en" case @config["lang"] when "en" return "#{key}_id(FK)" when "ja" return "#{key}ID(FK)" end end end