Skip to content

Instantly share code, notes, and snippets.

@JohnB
Created August 19, 2009 06:07
Show Gist options
  • Save JohnB/170200 to your computer and use it in GitHub Desktop.
Save JohnB/170200 to your computer and use it in GitHub Desktop.
=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