Skip to content
This repository has been archived by the owner on Aug 25, 2022. It is now read-only.

Commit

Permalink
Parse and display class graph on the output page.
Browse files Browse the repository at this point in the history
Also, open the page automatically after generation,
use all subdirectories of the app/models/ directory,
correctly reformat association class names in the parser,
and skip class_name: relations (which we will need to fix
later).
  • Loading branch information
alex-grover committed Apr 30, 2015
1 parent 8b90f0d commit 6213e5b
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 110 deletions.
21 changes: 15 additions & 6 deletions lib/model-visualizer/model-parser.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env ruby

require 'active_support/inflector'
require_relative 'model'

class ModelParser
Expand All @@ -12,13 +13,17 @@ def parse
abort 'app/models/ directory does not exist! Make sure you are in the root directory of your Rails project.'
end

# TODO: make sure we should only be grabbing model files, not subdirectories
files = Dir['app/models/*.rb']
files = Dir['app/models/**/*.rb']
curr_model = nil

# TODO: maybe factor this out into a parent class?
for file in files
IO.foreach(file) do |line|
# TODO: hack to skip associations, we need to display these as "has_many: Users AS Managers"
if line.include? 'class_name'
next
end

line.strip!
if line.start_with? 'class'
name = /class ([[:alpha:]]+)/.match(line)[1]
Expand All @@ -27,21 +32,25 @@ def parse

elsif line.start_with? 'has_many'
model_neighbor = /has_many :(\w+)/.match(line)[1]
curr_model.add_has_many(model_neighbor)
curr_model.add_has_many fix_case(model_neighbor)

elsif line.start_with? 'has_one'
model_neighbor = /has_one :(\w+)/.match(line)[1]
curr_model.add_has_one(model_neighbor)
curr_model.add_has_one fix_case(model_neighbor)

elsif line.start_with? 'has_and_belongs_to_many'
model_neighbor = /has_and_belongs_to_many :(\w+)/.match(line)[1]
curr_model.add_has_and_belongs_to_many(model_neighbor)
curr_model.add_has_and_belongs_to_many fix_case(model_neighbor)

elsif line.start_with? 'belongs_to'
model_neighbor = /belongs_to :(\w+)/.match(line)[1]
curr_model.add_belongs_to(model_neighbor)
curr_model.add_belongs_to fix_case(model_neighbor)
end
end
end
end

def fix_case(string)
ActiveSupport::Inflector.singularize(ActiveSupport::Inflector.camelize(string))
end
end
239 changes: 135 additions & 104 deletions lib/model-visualizer/visualizer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,115 +3,146 @@
require 'json'

class Visualizer
FILE_PATH = 'modelVisualizer.html'

def initialize(models)
@models = models
end

def create_visualization
fileHtml = File.new('modelVisualizer.html', 'w+')

fileHtml.puts '<!DOCTYPE html>
<html>
<meta charset="utf-8">
<style>
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script type="application/json" id="models">
{
"nodes": [{
"name": "User",
"group": 1
}, {
"name": "Project",
"group": 1
}, {
"name": "Data",
"group": 1
}],
"links": [{
"source": 0,
"target": 1,
"value": 1
}, {
"source": 1,
"target": 2,
"value": 1
}, {
"source": 0,
"target": 2,
"value": 1
}]
}
</script>
<script>
var width = 960,
height = 500;
var color = d3.scale.category20();
var force = d3.layout
.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var models = document.getElementById("models").innerHTML,
graph = JSON.parse(models);
force.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
.call(force.drag);
node.append("title")
.text(function(d) { return d.name; });
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
var data = '
# TODO: actually do something with data in JS
fileHtml.puts JSON.generate(@models)
fileHtml.puts '; console.log(data);'
fileHtml.puts '</script></body></html>'
fileHtml = File.new(FILE_PATH, 'w+')

fileHtml.puts '
<!DOCTYPE html>
<html lang="en-US">
<head>
<title>Rails Model Visualizer</title>
<meta charset="utf-8">
<style>
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
</style>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<script>
(function() {
var data = ' + JSON.generate(@models) + ';
var nodes = [];
var links = [];
// Create node array and number each one
var i = 0;
for (var key in data) {
if (data.hasOwnProperty(key)) {
data[key].node_number = i++;
nodes.push({
name: key,
group: 1
});
}
}
// Create links array using associations
for (var key in data) {
if (data.hasOwnProperty(key)) {
node = data[key];
for (var association_type in node.associations) {
node.associations[association_type].forEach(function(assn) {
links.push({
source: node.node_number,
target: data[assn].node_number,
type: association_type
});
});
}
}
}
var width = 960,
height = 500;
var color = d3.scale.category20();
var force = d3.layout
.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
force.nodes(nodes)
.links(links)
.start();
var link = svg.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
.style("stroke", function(d) {
switch(d.type) {
case "belongs_to":
return "#000";
case "has_and_belongs_to_many":
return "#f00";
case "has_many":
return "#0f0";
case "has_one":
return "#00f";
default:
return "#999";
}
}).style("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
.call(force.drag);
node.append("title")
.text(function(d) { return d.name; });
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
})();
</script>
</body>
</html>'
fileHtml.close()

self.launch_browser FILE_PATH
end

# http://stackoverflow.com/questions/152699/open-the-default-browser-in-ruby
def launch_browser(path)
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
system "start #{path}"
elsif RbConfig::CONFIG['host_os'] =~ /darwin/
system "open #{path}"
elsif RbConfig::CONFIG['host_os'] =~ /linux|bsd/
system "xdg-open #{path}"
end
end
end

0 comments on commit 6213e5b

Please sign in to comment.