Navigation Menu

Skip to content

Commit

Permalink
First public version.
Browse files Browse the repository at this point in the history
  • Loading branch information
Rolf Timmermans committed Sep 19, 2010
1 parent 2bb409f commit bb371f0
Show file tree
Hide file tree
Showing 23 changed files with 1,388 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
doc
4 changes: 4 additions & 0 deletions CHANGES.rdoc
@@ -0,0 +1,4 @@
=== 0.1.0

* Released on September 20th, 2010.
* First public release.
19 changes: 19 additions & 0 deletions LICENSE
@@ -0,0 +1,19 @@
Copyright (c) 2010 Voormedia

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
56 changes: 56 additions & 0 deletions README.rdoc
@@ -0,0 +1,56 @@
= Rails ERD - Generate Entity-Relationship Diagrams for Rails applications

Rails ERD is a Rails plugin that allows you to easily generate diagrams based
on your ActiveRecord models. The diagrams give a great overview of how your
models are related. Having a diagram that describes your models is perfect
documentation for your application.

Rails ERD was created specifically for Rails 3. It uses ActiveRecord reflection
to figure out how your models are associated.

== Getting started

In its most simple form, Rails ERD is a plugin for Rails 3 that provides you
with a Rake task to create an Entity-Relationship Diagram. It depends on the
Graphviz visualisation library. You have to install Graphviz before using
Rails ERD. In order to create PDF files (the default), you should install
or compile Graphviz with Pango/Cairo.

For example, to install Graphviz with MacPorts:

% sudo port install graphviz

Or with Homebrew:

% brew install cairo pango graphviz

Next, install Rails ERD. Open your +Gemfile+, and add the following:

group :development do
gem 'rails-erd'
end

Tell Bundler to install Rails ERD:

% bundle install

You now have access to Rails ERD through Rake. Generate a new
Entity-Relationship Diagram for your Rails application:

% rake erd

All done! You will now have a file named +ERD.pdf+ in your application root.

== Advanced use

Rails ERD has several options that you can use to customise its behaviour.
All options can be provided on the command line. For example:

% rake erd exclude_timestamps=false

For an overview of all available options, see the documentation of RailsERD:
http://rails-erd.rubyforge.org/doc/

== License

Rails ERD is released under the MIT license.
38 changes: 38 additions & 0 deletions Rakefile
@@ -0,0 +1,38 @@
# encoding: utf-8
require "jeweler"
require "rake/testtask"

Jeweler::Tasks.new do |spec|
spec.name = "rails-erd"
spec.rubyforge_project = "rails-erd"
spec.summary = "Entity-relationship diagram for your Rails models."
spec.description = "Automatically generate an entity-relationship diagram (ERD) for the models in your Rails application."

spec.authors = ["Rolf Timmermans"]
spec.email = "r.timmermans@voormedia.com"
spec.homepage = "http://rails-erd.rubyforge.org/"

spec.add_runtime_dependency "activesupport", "~> 3.0.0"
spec.add_runtime_dependency "ruby-graphviz", "~> 0.9.17"
end

Jeweler::GemcutterTasks.new

Jeweler::RubyforgeTasks.new do |rubyforge|
rubyforge.doc_task = "rdoc"
end

Rake::TestTask.new do |test|
test.pattern = "test/unit/**/*_test.rb"
end

begin
require "hanna/rdoctask"
Rake::RDocTask.new do |rdoc|
rdoc.rdoc_files = Dir["[A-Z][A-Z]*"] + Dir["lib/**/*.rb"]
rdoc.title = "Rails ERD – Entity-Relationship Diagrams for Rails"
rdoc.rdoc_dir = "doc"
end
rescue => e
puts e.message
end
1 change: 1 addition & 0 deletions lib/rails-erd.rb
@@ -0,0 +1 @@
require "rails_erd"
38 changes: 38 additions & 0 deletions lib/rails_erd.rb
@@ -0,0 +1,38 @@
require "active_support/ordered_options"
require "rails_erd/railtie" if defined? Rails

# Rails ERD provides several options that allow you to customise the
# generation of the diagram and the domain model itself. Currently, the
# following options are supported:
#
# type:: The file type of the generated diagram. Defaults to +:pdf+, which
# is the recommended format. Other formats may render significantly
# worse than a PDF file.
# orientation:: The direction of the hierarchy of entities. Either +:horizontal+
# or +:vertical+. Defaults to +:horizontal+.
# suppress_warnings:: When set to +true+, no warnings are printed to the
# command line while processing the domain model. Defaults
# to +false+.
# exclude_timestamps:: Excludes timestamp columns (<tt>created_at/on</tt> and
# <tt>updated_at/on</tt>) from attribute lists. Defaults
# to +true+.
# exclude_primary_keys:: Excludes primary key columns from attribute lists.
# Defaults to +true+.
# exclude_foreign_keys:: Excludes foreign key columns from attribute lists.
# Defaults to +true+.
module RailsERD
class << self
# Access to default options. Any instance of RailsERD::Domain and
# RailsERD::Diagram will use these options unless overridden.
attr_accessor :options
end

self.options = ActiveSupport::OrderedOptions[
:type, :pdf,
:orientation, :horizontal,
:exclude_timestamps, true,
:exclude_primary_keys, true,
:exclude_foreign_keys, true,
:suppress_warnings, false
]
end
74 changes: 74 additions & 0 deletions lib/rails_erd/attribute.rb
@@ -0,0 +1,74 @@
# -*- encoding: utf-8
module RailsERD
class Attribute
TIMESTAMP_NAMES = %w{created_at created_on updated_at updated_on} #:nodoc:

class << self
def from_model(domain, model) #:nodoc:
model.arel_table.columns.collect { |column| Attribute.new(domain, model, column) }.sort
end
end

attr_reader :column #:nodoc:

def initialize(domain, model, column)
@domain, @model, @column = domain, model, column
end

def name
column.name
end

def type
column.type
end

def mandatory?
!column.null or @model.validators_on(name).map(&:kind).include?(:presence)
end

def primary_key?
@model.arel_table.primary_key == name
end

def foreign_key?
@domain.relationships_for(@model).map(&:associations).flatten.map(&:primary_key_name).include?(name)
end

def timestamp?
TIMESTAMP_NAMES.include? name
end

def <=>(other) #:nodoc:
name <=> other.name
end

def inspect #:nodoc:
"#<#{self.class.name}:0x%.14x @column=#{name.inspect} @type=#{type.inspect}>" % (object_id << 1)
end

def to_s #:nodoc:
name
end

def type_description
case type
when :integer then "int"
when :float then "float"
when :decimal then "dec"
when :datetime then "datetime"
when :date then "date"
when :timestamp then "timest"
when :time then "time"
when :text then "txt"
when :string then "str"
when :binary then "blob"
when :boolean then "bool"
else type.to_s
end.tap do |desc|
desc << " (#{column.limit})" if column.limit != @model.connection.native_database_types[type][:limit]
desc << " ∗" if mandatory? # Add a hair space + low asterisk (Unicode characters).
end
end
end
end
88 changes: 88 additions & 0 deletions lib/rails_erd/diagram.rb
@@ -0,0 +1,88 @@
require "rails_erd/domain"
require "graphviz"

module RailsERD
class Diagram
NODE_LABEL_TEMPLATE = File.read(File.expand_path("templates/node.erb", File.dirname(__FILE__))) #:nodoc:
NODE_WIDTH = 130 #:nodoc:

class << self
def generate(options = {})
new(Domain.generate(options), options).output
end
end

attr_reader :options #:nodoc:

def initialize(domain, options = {})
@domain, @options = domain, RailsERD.options.merge(options)
end

def graph
@graph ||= GraphViz.new(@domain.name, :type => :digraph) do |graph|
graph[:rankdir] = horizontal? ? :LR : :TB
graph[:ranksep] = 0.5
graph[:nodesep] = 0.35
graph[:margin] = "0.4,0.4"
graph[:concentrate] = true
graph[:label] = "#{@domain.name} domain model\\n\\n"
graph[:labelloc] = :t
graph[:fontsize] = 13
graph[:fontname] = "Arial Bold"
graph[:remincross] = true
graph[:outputorder] = :edgesfirst

graph.node[:shape] = "Mrecord"
graph.node[:fontsize] = 10
graph.node[:fontname] = "Arial"
graph.node[:margin] = "0.07,0.05"

graph.edge[:fontname] = "Arial"
graph.edge[:fontsize] = 8
graph.edge[:dir] = :both
graph.edge[:arrowsize] = 0.8

nodes = {}

@domain.entities.select(&:connected?).each do |entity|
attributes = entity.attributes.reject { |attribute|
options.exclude_primary_keys && attribute.primary_key? or
options.exclude_foreign_keys && attribute.foreign_key? or
options.exclude_timestamps && attribute.timestamp?
}

nodes[entity] = graph.add_node entity.name, :html => ERB.new(NODE_LABEL_TEMPLATE, nil, "<>").result(binding)
end

@domain.relationships.each do |relationship|
options = {}
options[:arrowhead] = relationship.cardinality.one_to_one? ? :dot : :normal
options[:arrowtail] = relationship.cardinality.many_to_many? ? :normal : :dot
options[:weight] = relationship.strength
options.merge! :style => :dashed, :constraint => false if relationship.indirect?

graph.add_edge nodes[relationship.source], nodes[relationship.destination], options
end
end
end

def output
graph.output(options.type.to_sym => file_name)
self
end

def file_name
"ERD.#{options.type}"
end

private

def horizontal?
options.orientation == :horizontal
end

def vertical?
!horizontal?
end
end
end

0 comments on commit bb371f0

Please sign in to comment.