Created
September 18, 2012 17:06
-
-
Save markjlorenz/3744338 to your computer and use it in GitHub Desktop.
Rails and Coffee to render an object graph for any rails model
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
#As coded this is suiteable for adding to ApplicationController, or an included controller module | |
#It would also be cool to refactor this code to be added to fat models instead | |
def object_graph | |
@prune_nodes = params[:prune_nodes] #When the crawler hits a node of this class, it will stop | |
model_name = controller_name.classify | |
root_object = model_name.classify.constantize.find(params[:id]) | |
edge_list = {} #This is implemented as an object instead of an array for lookup performance reasons | |
nodes = {} | |
object_ident = ->(object){"#{object.id}-#{object.class}"} #helper function | |
fill_object_data = ->(object, ident) do #add some data to the JSON object that will be used by the renderer | |
edge_list[ident] = [] if !edge_list[ident] #initialize if needed | |
unless nodes[ident] | |
nodes[ident] = { | |
id:object.id, | |
type:object.class.to_s, | |
href:(url_for(object) rescue nil), #if there are no routes for this object leave it blank | |
name:ident, | |
description: (object.web_description rescue "") #if you add #web_description to the models it will be used here | |
} | |
end | |
end | |
traverse = ->(object) do #DFS over the model object graph by reflecting on associations | |
object.class.reflect_on_all_associations.each do |assoc| | |
this_object_ident = object_ident.call object | |
fill_object_data.call object, this_object_ident | |
associated_objects = Array.wrap object.send(assoc.name) #Call the association on the object i.e. `foo.bars` | |
associated_objects.each do |assoc_obj| | |
assoc_obj_ident = object_ident.call assoc_obj | |
edge_list[this_object_ident].push assoc_obj_ident | |
next if edge_list[assoc_obj_ident] #if we've already seen it, skip it | |
unless @prune_nodes.include? assoc_obj.class.to_s.underscore #if it's on the prune list, add it, but don't recurse | |
traverse.call(assoc_obj) | |
else | |
fill_object_data.call assoc_obj, assoc_obj_ident | |
end | |
end | |
end | |
end | |
traverse.call(root_object) | |
@json = {rootId:object_ident.call(root_object), edges:edge_list, nodes:nodes}.to_json | |
render '/application/object_graph' #keep the view in a common folder | |
end |
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
#using the [VivaGraph library](https://github.com/anvaka/VivaGraphJS) to build a nice looking graph | |
#this examples requires Underscore.js and the jQuery-ui theme roller icons | |
ObjectGraph = (rootId, nodes, nodeLinks)-> | |
graph = Viva.Graph.graph() | |
_.each nodes, (node, node_id)-> | |
graph.addNode node_id, node | |
_.each nodeLinks, (nodeLinkList, key)-> | |
_.each nodeLinkList, (nodeLink)-> | |
graph.addLink(key, nodeLink) | |
graphics = Viva.Graph.View.svgGraphics() | |
graphics.node (node)-> #build the node element from SVG and HTML components | |
fillColor = if node.data.name == rootId then 'red' else 'blue' | |
svg = Viva.Graph.svg('svg') | |
g = svg.appendChild Viva.Graph.svg('g') | |
g.appendChild Viva.Graph.svg('rect').attr('width', 10).attr('height', 10).attr('fill', fillColor) | |
#foreign element sizeing could be done a lot better than what I have here | |
foreignOject = g.appendChild Viva.Graph.svg('foreignObject').attr('width', '13em').attr('height', '2em').attr('fill', fillColor) | |
body = foreignOject.appendChild document.createElementNS('http://www.w3.org/1999/xhtml', 'body') | |
div = body.appendChild document.createElement('div') | |
div.appendChild(document.createElement 'span' ).textContent = node.data.type | |
div2 = body.appendChild document.createElement('div') | |
div2.appendChild(document.createElement 'span').textContent = node.data.description | |
if node.data.href | |
a = div.appendChild document.createElement('a') | |
a.setAttribute('href', node.data.href) | |
linkIcon = a.appendChild document.createElement('span') | |
linkIcon.setAttribute('class', "ui-icon ui-icon-link") | |
linkIcon.setAttribute('style', "display:inline-block") #so icon and text are on the same line, using the style sheet didn't work for me | |
svg | |
renderer = Viva.Graph.View.renderer graph, {graphics:graphics, container:document.getElementById('your_ele_id')} | |
renderer.run() |
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
<% if @prune_nodes %> | |
<ul class='inline'> | |
<li>Network Pruned Nodes:</li> | |
<% @prune_nodes.each do |prune|%> | |
<li><%= raw prune.titleize %> </li> | |
<% end %> | |
</ul> | |
<% end %> | |
<script type="text/javascript"> | |
json = <%= raw @json %> | |
ObjectGraph(json.rootId, json.nodes, json.edges); | |
</script> | |
<style type="text/css"> | |
svg{ overflow:visible !important; } /*easier than trying to size the SVG element right*/ | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment