Created
August 19, 2009 06:07
-
-
Save JohnB/170200 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
=begin | |
Read the db/schema.rb file and churn out a .DOT file containing | |
tables and linkages for *all* tables, not just the ones that | |
have ActiveRecord models. Guess that columns ending in _id are | |
links to other tables; assume all relations are one-to-many. | |
BACKGROUND | |
I'm working with a schema that is shared by a Rails app and a C++ app. | |
Some tables are only accessed by the C++ code and have no need to be | |
represented in the Rails models. A side-effect of this is that a tool | |
like the Railroad gem only shows half the schema. So I wrote this code | |
to create a schema diagram similar to what Railroad creates. | |
It requires the graphviz "dot" appliation to do the final rendering from | |
a .DOT file to a .JPG file. | |
USAGE (from your Rails root): | |
schema2dot.rb > full_models.dot | |
dot -Tjpg full_models.dot > full_models.jpg | |
CAVEATS: | |
1) we treat everything as a one-to-many relationship, so if you have | |
a "has one" relationship we will fail to show an empty circle on the | |
"one" side, which would correctly (if I understand UML) denote that | |
it was a has-one instead of a has-many relationship. | |
2) Requires that the entire schema is represented in the db/schema.rb | |
file (i.e. that Rails migrations created all the tables). In theory | |
the C++ code could separately create the tables that it "owns" but | |
that would likely be unworkable in the long term. | |
TODOs that I'll probably never do: | |
- use the Rails-standard singularize() method instead of just | |
dropping the final 's' of the table name. | |
=end | |
def convert_column_to_link column_name | |
cn = column_name.dup | |
if cn =~ /^(.*)_id$/ | |
return $1 | |
else | |
return nil | |
end | |
end | |
def interesting_table table_name | |
true #your schema-specific filter goes here | |
end | |
DOT_OUTPUT = 'digraph models_diagram { | |
graph[overlap=false, splines=true] | |
#{linked_tables_str} | |
#{linkages_str} | |
}' | |
TABLE_LINE = ' \"#{table_name}\" [shape=Mrecord, label=\"{#{table_name}#{table_columns}}\"]' | |
LINK_LINE = ' \"#{has_many_table_name}\" -> \"#{table_name}\" [arrowtail=crow, arrowhead=dot, dir=both]' | |
TABLE_REGEXP = /\screate_table\s+"(\w+)s"(.*?)\s+end/m | |
ROW_REGEXP = /t\.column\s+"(\w+)",\s+\:(\w+)/m | |
schema = IO.read('db/schema.rb') | |
tables = {} | |
linkages = {} | |
linked_tables = {} | |
schema.scan(TABLE_REGEXP).each do |table_name, table_contents| | |
if interesting_table(table_name) | |
tables[table_name] ||= [] | |
linkages[table_name] ||= [] | |
columns = table_contents.scan(ROW_REGEXP).each do |column_name, column_type| | |
tables[table_name] << [column_name, column_type] | |
link_name = convert_column_to_link(column_name) | |
if link_name | |
linkages[table_name] << link_name | |
linked_tables[table_name] = true | |
linked_tables[link_name] = true | |
end | |
end | |
end | |
end | |
linked_tables_array = [] | |
linkages_array = [] | |
linked_tables.keys.sort.each do |table_name| | |
if tables[table_name] | |
table_columns = tables[table_name].collect { |ar| "#{ar.first} :#{ar.last}\\l" }.join | |
table_columns = '|'+table_columns if table_columns | |
linked_tables_array << eval('"'+TABLE_LINE+'"') | |
linkages[table_name].uniq.sort.each do |has_many_table_name| | |
if tables.keys.include? has_many_table_name | |
linkages_array << eval('"'+LINK_LINE+'"') | |
end | |
end | |
end | |
end | |
linked_tables_str = linked_tables_array.join("\n") | |
linkages_str = linkages_array.join("\n") | |
result = eval('"'+DOT_OUTPUT+'"') | |
puts result |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment