Skip to content

Commit

Permalink
add support for merb+datamapper
Browse files Browse the repository at this point in the history
  • Loading branch information
royw committed Dec 15, 2008
1 parent 294bfb2 commit 3839af0
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 131 deletions.
13 changes: 8 additions & 5 deletions README
@@ -1,15 +1,15 @@
= RailRoad

RailRoad generates models and controllers diagrams in DOT language for a
Rails application.
Rails or Merb application.


= Usage

Run RailRoad on the Rails application's root directory. You can redirect its
output to a .dot file or pipe it to the dot or neato utilities to produce a
graphic. Model diagrams are intended to be processed using dot and
controller diagrams are best processed using neato.
Run RailRoad on the Rails or Merbs application's root directory. You can
redirect its output to a .dot file or pipe it to the dot or neato utilities
to produce a graphic. Model diagrams are intended to be processed using dot
and controller diagrams are best processed using neato.

railroad [options] command

Expand Down Expand Up @@ -51,6 +51,7 @@ Other options:
-M, --models Generate models diagram
-C, --controllers Generate controllers diagram
-A, --aasm Generate "acts as state machine" diagram
(Rails only)


== Examples
Expand Down Expand Up @@ -125,6 +126,8 @@ RailRoad has been tested with Ruby 1.8.5 and Rails 1.1.6 to 1.2.3
applications. There is no additional requirements (nevertheless, all your
Rails application requirements must be installed).

RailRoad has been tested with Merb 1.0.4 and DataMapper 0.9.7.

In order to view/export the DOT diagrams, you'll need the processing tools
from Graphviz.

Expand Down
4 changes: 4 additions & 0 deletions lib/railroad/aasm_diagram.rb
Expand Up @@ -34,6 +34,10 @@ def generate

# Load model classes
def load_classes
unless @framework.name == 'Rails'
print_error 'AASM diagrams only supported for Rails'
raise LoadError.new
end
begin
disable_stdout
files = Dir.glob("app/models/**/*.rb")
Expand Down
33 changes: 25 additions & 8 deletions lib/railroad/app_diagram.rb
Expand Up @@ -5,6 +5,7 @@
# See COPYING for more details

require 'railroad/diagram_graph'
require 'railroad/framework_factory'

# Root class for RailRoad diagrams
class AppDiagram
Expand Down Expand Up @@ -34,11 +35,11 @@ def print
end

if @options.xmi
STDERR.print "Generating XMI diagram\n" if @options.verbose
STDERR.print "Generating XMI diagram\n" if @options.verbose
STDOUT.print @graph.to_xmi
else
STDERR.print "Generating DOT graph\n" if @options.verbose
STDOUT.print @graph.to_dot
STDERR.print "Generating DOT graph\n" if @options.verbose
STDOUT.print @graph.to_dot
end

if @options.output
Expand All @@ -63,27 +64,43 @@ def enable_stdout
# Print error when loading Rails application
def print_error(type)
STDERR.print "Error loading #{type}.\n (Are you running " +
"#{APP_NAME} on the aplication's root directory?)\n\n"
"#{APP_NAME} on the application's root directory?)\n\n"
end

# Load Rails application's environment
def load_environment
begin
disable_stdout
require "config/environment"
@framework = FrameworkFactory.getFramework
raise LoadError.new if @framework.nil?
@graph.migration_version = @framework.migration_version
enable_stdout
rescue LoadError
enable_stdout
print_error "application environment"
raise
end
end

# is the given class a subclass of the application controller?
def is_application_subclass?(klass)
@framework.is_application_subclass?(klass)
end

# get the controller's files returning the application controller first in returned array
def get_controller_files(options)
@framework.get_controller_files(options)
end

# Extract class name from filename
def extract_class_name(filename)
#filename.split('/')[2..-1].join('/').split('.').first.camelize
# Fixed by patch from ticket #12742
File.basename(filename).chomp(".rb").camelize
@framework.extract_class_name(filename)
end

# convert the give string to a constant
def constantize(str)
@framework.constantize(str)
end

end # class AppDiagram

13 changes: 4 additions & 9 deletions lib/railroad/controllers_diagram.rb
Expand Up @@ -19,13 +19,10 @@ def initialize(options)
def generate
STDERR.print "Generating controllers diagram\n" if @options.verbose

files = Dir.glob("app/controllers/**/*_controller.rb") - @options.exclude
files << 'app/controllers/application.rb'
files = get_controller_files(@options)
files.each do |f|
class_name = extract_class_name(f)
# ApplicationController's file is 'application.rb'
class_name += 'Controller' if class_name == 'Application'
process_class class_name.constantize
process_class constantize(class_name)
end
end # generate

Expand All @@ -36,8 +33,7 @@ def load_classes
begin
disable_stdout
# ApplicationController must be loaded first
require "app/controllers/application.rb"
files = Dir.glob("app/controllers/**/*_controller.rb") - @options.exclude
files = get_controller_files(@options)
files.each {|c| require c }
enable_stdout
rescue LoadError
Expand Down Expand Up @@ -74,8 +70,7 @@ def process_class(current_class)
end

# Generate the inheritance edge (only for ApplicationControllers)
if @options.inheritance &&
(ApplicationController.subclasses.include? current_class.name)
if @options.inheritance && is_application_subclass?(current_class)
@graph.add_edge ['is-a', current_class.superclass.name, current_class.name]
end
end # process_class
Expand Down
24 changes: 14 additions & 10 deletions lib/railroad/diagram_graph.rb
Expand Up @@ -7,8 +7,9 @@

# RailRoad diagram structure
class DiagramGraph

attr_accessor :migration_version
def initialize
@migration_version = nil
@diagram_type = ''
@show_label = false
@nodes = []
Expand All @@ -35,8 +36,8 @@ def show_label= (value)
# Generate DOT graph
def to_dot
return dot_header +
@nodes.map{|n| dot_node n[0], n[1], n[2]}.join +
@edges.map{|e| dot_edge e[0], e[1], e[2], e[3]}.join +
@nodes.uniq.map{|n| dot_node n[0], n[1], n[2]}.join +
@edges.uniq.map{|e| dot_edge e[0], e[1], e[2], e[3]}.join +
dot_footer
end

Expand All @@ -63,13 +64,16 @@ def dot_footer

# Build diagram label
def dot_label
return "\t_diagram_info [shape=\"plaintext\", " +
"label=\"#{@diagram_type} diagram\\l" +
"Date: #{Time.now.strftime "%b %d %Y - %H:%M"}\\l" +
"Migration version: " +
"#{ActiveRecord::Migrator.current_version}\\l" +
"Generated by #{APP_HUMAN_NAME} #{APP_VERSION.join('.')}"+
"\\l\", fontsize=14]\n"
buf = []
buf << "\t_diagram_info [shape=\"plaintext\", "
buf << "label=\"#{@diagram_type} diagram\\l"
buf << "Date: #{Time.now.strftime "%b %d %Y - %H:%M"}\\l"
unless @migration_version.nil?
buf << "Migration version: #{@migration_version}\\l"
end
buf << "Generated by #{APP_HUMAN_NAME} #{APP_VERSION.join('.')}"
buf << "\\l\", fontsize=14]\n"
buf.join('')
end

# Build a DOT graph node
Expand Down
126 changes: 27 additions & 99 deletions lib/railroad/models_diagram.rb
Expand Up @@ -5,6 +5,7 @@
# See COPYING for more details

require 'railroad/app_diagram'
require 'railroad/model_factory'

# RailRoad models diagram
class ModelsDiagram < AppDiagram
Expand All @@ -13,8 +14,6 @@ def initialize(options)
#options.exclude.map! {|e| "app/models/" + e}
super options
@graph.diagram_type = 'Models'
# Processed habtm associations
@habtm = []
end

# Process model files
Expand All @@ -24,7 +23,7 @@ def generate
files += Dir.glob("vendor/plugins/**/app/models/*.rb") if @options.plugins_models
files -= @options.exclude
files.each do |f|
process_class extract_class_name(f).constantize
process_class constantize(extract_class_name(f))
end
end

Expand All @@ -45,111 +44,40 @@ def load_classes
raise
end
end # load_classes

# Process a model class
def process_class(current_class)

STDERR.print "\tProcessing #{current_class}\n" if @options.verbose

generated = false

# Is current_clas derived from ActiveRecord::Base?
if current_class.respond_to?'reflect_on_all_associations'


node_attribs = []
if @options.brief || current_class.abstract_class?
node_type = 'model-brief'
else
node_type = 'model'

# Collect model's content columns

content_columns = current_class.content_columns

if @options.hide_magic
# From patch #13351
# http://wiki.rubyonrails.org/rails/pages/MagicFieldNames
magic_fields = [
"created_at", "created_on", "updated_at", "updated_on",
"lock_version", "type", "id", "position", "parent_id", "lft",
"rgt", "quote", "template"
]
magic_fields << current_class.table_name + "_count" if current_class.respond_to? 'table_name'
content_columns = current_class.content_columns.select {|c| ! magic_fields.include? c.name}
else
content_columns = current_class.content_columns
end

content_columns.each do |a|
content_column = a.name
content_column += ' :' + a.type.to_s unless @options.hide_types
node_attribs << content_column
end
end
@graph.add_node [node_type, current_class.name, node_attribs]
generated = true
# Process class associations
associations = current_class.reflect_on_all_associations
if @options.inheritance && ! @options.transitive
superclass_associations = current_class.superclass.reflect_on_all_associations

associations = associations.select{|a| ! superclass_associations.include? a}
# This doesn't works!
# associations -= current_class.superclass.reflect_on_all_associations
end
associations.each do |a|
process_association current_class.name, a
model = ModelFactory.getModel(current_class, @options)
node_attribs = []
edges = []
nodes = []
if @options.brief || model.abstract?
node_type = 'model-brief'
else
node_type = 'model'
node_attribs += model.attributes unless model.nil?
end
nodes << [node_type, current_class.name, node_attribs]

if !model.nil?
edges += model.edges
# Only consider meaningful inheritance relations classes
if @options.inheritance && model.meaningful?
edges << ['is-a', current_class.superclass.name, current_class.name]
end
elsif @options.all && (current_class.is_a? Class)
# Not ActiveRecord::Base model
# Not database model
node_type = @options.brief ? 'class-brief' : 'class'
@graph.add_node [node_type, current_class.name]
generated = true
nodes << [node_type, current_class.name]
edges << ['is-a', current_class.superclass.name, current_class.name]
elsif @options.modules && (current_class.is_a? Module)
@graph.add_node ['module', current_class.name]
nodes << ['module', current_class.name]
end

# Only consider meaningful inheritance relations for generated classes
if @options.inheritance && generated &&
(current_class.superclass != ActiveRecord::Base) &&
(current_class.superclass != Object)
@graph.add_edge ['is-a', current_class.superclass.name, current_class.name]
end

nodes.compact.each {|node| @graph.add_node node}
edges.compact.each {|edge| @graph.add_edge edge}
end # process_class

# Process a model association
def process_association(class_name, assoc)

STDERR.print "\t\tProcessing model association #{assoc.name.to_s}\n" if @options.verbose

# Skip "belongs_to" associations
return if assoc.macro.to_s == 'belongs_to'

# Only non standard association names needs a label

# from patch #12384
# if assoc.class_name == assoc.name.to_s.singularize.camelize
assoc_class_name = (assoc.class_name.respond_to? 'underscore') ? assoc.class_name.underscore.singularize.camelize : assoc.class_name
if assoc_class_name == assoc.name.to_s.singularize.camelize
assoc_name = ''
else
assoc_name = assoc.name.to_s
end

if assoc.macro.to_s == 'has_one'
assoc_type = 'one-one'
elsif assoc.macro.to_s == 'has_many' && (! assoc.options[:through])
assoc_type = 'one-many'
else # habtm or has_many, :through
return if @habtm.include? [assoc.class_name, class_name, assoc_name]
assoc_type = 'many-many'
@habtm << [class_name, assoc.class_name, assoc_name]
end
# from patch #12384
# @graph.add_edge [assoc_type, class_name, assoc.class_name, assoc_name]
@graph.add_edge [assoc_type, class_name, assoc_class_name, assoc_name]
end # process_association


end # class ModelsDiagram

0 comments on commit 3839af0

Please sign in to comment.