rails-erd 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -5,5 +5,6 @@
5
5
  *.rbc
6
6
  doc
7
7
  pkg
8
+ output
8
9
  rdoc
9
10
  site/_generated
data/CHANGES.rdoc CHANGED
@@ -1,3 +1,19 @@
1
+ === 0.4.0:
2
+
3
+ * Support to optionally display single table inheritance relationships
4
+ (inheritance=true).
5
+ * Support to optionally display polymorphic associations (polymorphism=true).
6
+ * Adjustments to 'advanced' style so that it matches original Bachman style,
7
+ and therefore now called 'bachman'.
8
+ * Ignore models without tables (reported by Mark Chapman).
9
+ * Mutual indirect relationships are now combined.
10
+ * Changed API for diagram generation.
11
+ * Restructured classes and renamed several API properties and methods.
12
+ * Added new edge type to describe single table inheritance and polymorphic
13
+ associations: Specialization.
14
+ * Added compatibility for Active Record 3.1 (beta), removed dependency on Arel.
15
+ * Rubinius compatibility.
16
+
1
17
  === 0.3.0:
2
18
 
3
19
  * Added the ability to support multiple styles of cardinality notations.
@@ -10,7 +26,7 @@
10
26
  * More versatile API that allows you to inspect relationships and their
11
27
  cardinalities.
12
28
  * Changed line widths to 1.0 to avoid invisible node boundaries with older
13
- versions of Graphviz.
29
+ versions of Graphviz (reported by Mike McQuinn).
14
30
  * Bundled examples based on actual applications.
15
31
 
16
32
  === 0.2.0
data/Gemfile CHANGED
@@ -1,8 +1,8 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gem "rails-erd", :path => "."
4
- gem "activesupport", :require => false
5
- gem "activerecord", :require => "active_record"
4
+ gem "activerecord"
5
+ gem "activesupport"
6
6
  gem "rake"
7
7
  gem "jeweler"
8
8
 
@@ -13,4 +13,5 @@ end
13
13
  platforms :jruby do
14
14
  gem "jdbc-sqlite3", :require => "jdbc/sqlite3"
15
15
  gem "activerecord-jdbc-adapter", "1.0.0.beta2"
16
+ gem "jruby-openssl", :require => false # Silence openssl warnings.
16
17
  end
data/Gemfile.lock CHANGED
@@ -1,10 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rails-erd (0.2.0)
5
- activerecord (~> 3.0.0)
4
+ rails-erd (0.3.0)
5
+ activerecord (~> 3.0)
6
6
  activesupport (~> 3.0)
7
- ruby-graphviz (~> 0.9.17)
7
+ ruby-graphviz (~> 0.9.18)
8
8
 
9
9
  GEM
10
10
  remote: http://rubygems.org/
@@ -22,6 +22,7 @@ GEM
22
22
  activesupport (3.0.0)
23
23
  arel (1.0.1)
24
24
  activesupport (~> 3.0.0)
25
+ bouncy-castle-java (1.5.0145.2)
25
26
  builder (2.1.2)
26
27
  gemcutter (0.6.1)
27
28
  git (1.2.5)
@@ -31,9 +32,11 @@ GEM
31
32
  gemcutter (>= 0.1.0)
32
33
  git (>= 1.2.5)
33
34
  rubyforge (>= 2.0.0)
35
+ jruby-openssl (0.7.1)
36
+ bouncy-castle-java
34
37
  json_pure (1.4.6)
35
38
  rake (0.8.7)
36
- ruby-graphviz (0.9.17)
39
+ ruby-graphviz (0.9.18)
37
40
  rubyforge (2.0.4)
38
41
  json_pure (>= 1.1.7)
39
42
  sqlite3-ruby (1.3.1)
@@ -49,6 +52,7 @@ DEPENDENCIES
49
52
  activesupport
50
53
  jdbc-sqlite3
51
54
  jeweler
55
+ jruby-openssl
52
56
  rails-erd!
53
57
  rake
54
58
  sqlite3-ruby
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+ Rails ERD - Generate Entity-Relationship Diagrams for Rails applications
2
+ ========================================================================
3
+
4
+ [Rails ERD](http://rails-erd.rubyforge.org/) is a Rails plugin that allows
5
+ you to easily generate a diagram based on your Active Record models. The
6
+ diagram gives an overview of how your models are related. Having a diagram
7
+ that describes your models is perfect documentation for your application.
8
+
9
+ The second goal of Rails ERD is to provide you with a tool to inspect your
10
+ application's domain model. If you don't like the default output, it is very
11
+ easy to use the API to build your own diagrams.
12
+
13
+ Rails ERD was created specifically for Rails 3. It uses Active Record's
14
+ built-in reflection capabilities to figure out how your models are associated.
15
+
16
+
17
+ Preview
18
+ -------
19
+
20
+ Here's an example entity-relationship diagram that was generated by Rails ERD:
21
+
22
+ ![Entity-Relationship Diagram](http://rails-erd.rubyforge.org/images/entity-relationship-diagram.png)
23
+
24
+ Browse the [gallery](http://rails-erd.rubyforge.org/gallery.html) for more
25
+ example diagrams.
26
+
27
+
28
+ Getting started
29
+ ---------------
30
+
31
+ See the [installation instructions](http://rails-erd.rubyforge.org/install.html)
32
+ for a complete description of how to install Rails ERD. Here's a summary:
33
+
34
+ * Install Graphviz 2.22+ with Pango and Cairo support ([how?](http://rails-erd.rubyforge.org/install.html))
35
+
36
+ * Add <tt>gem "rails-erd"</tt> to your application's Gemfile
37
+
38
+ * Run <tt>rake erd</tt>
39
+
40
+
41
+ Learn more
42
+ ----------
43
+
44
+ More information can be found on [Rails ERD's project homepage](http://rails-erd.rubyforge.org/).
45
+
46
+ If you wish to extend or customise Rails ERD, take a look at the [API documentation](http://rails-erd.rubyforge.org/doc/).
47
+
48
+
49
+ About Rails ERD
50
+ ---------------
51
+
52
+ Rails ERD was created by Rolf Timmermans (r.timmermans *at* voormedia.com)
53
+
54
+ Copyright 2010 Voormedia - [www.voormedia.com](http://www.voormedia.com/)
55
+
56
+
57
+ License
58
+ -------
59
+
60
+ Rails ERD is released under the MIT license.
data/Rakefile CHANGED
@@ -12,9 +12,9 @@ Jeweler::Tasks.new do |spec|
12
12
  spec.email = "r.timmermans@voormedia.com"
13
13
  spec.homepage = "http://rails-erd.rubyforge.org/"
14
14
 
15
- spec.add_runtime_dependency "activerecord", "~> 3.0.0"
15
+ spec.add_runtime_dependency "activerecord", "~> 3.0"
16
16
  spec.add_runtime_dependency "activesupport", "~> 3.0"
17
- spec.add_runtime_dependency "ruby-graphviz", "~> 0.9.17"
17
+ spec.add_runtime_dependency "ruby-graphviz", "~> 0.9.18"
18
18
  spec.add_development_dependency "sqlite3-ruby"
19
19
 
20
20
  # Don't bundle examples or website in gem.
@@ -39,61 +39,21 @@ task :default => :test
39
39
  begin
40
40
  require "hanna/rdoctask"
41
41
  Rake::RDocTask.new do |rdoc|
42
- rdoc.rdoc_files = Dir["[A-Z][A-Z]*"] + Dir["lib/**/*.rb"]
43
- rdoc.title = "Rails ERD – Entity-Relationship Diagrams for Rails"
42
+ rdoc.rdoc_files = %w{CHANGES.rdoc LICENSE} + Dir["lib/**/*.rb"]
43
+ rdoc.title = "Rails ERD – API Documentation"
44
44
  rdoc.rdoc_dir = "rdoc"
45
+ rdoc.main = "RailsERD"
45
46
  end
46
47
  rescue LoadError
47
48
  end
48
49
 
49
50
  desc "Generate diagrams for bundled examples"
50
51
  task :examples do
51
- require "rubygems"
52
- require "bundler"
53
- Bundler.require
54
- require "rails_erd/diagram/graphviz"
55
-
56
- Dir["examples/*/*"].each do |path|
57
- name = File.basename(path)
58
- print "==> Generating ERD for #{name.capitalize}... "
59
- begin
60
- # Load database schema.
61
- ActiveRecord::Base.establish_connection :adapter => "sqlite3", :database => ":memory:"
62
- ActiveRecord::Migration.suppress_messages do
63
- begin
64
- require File.expand_path("#{path}/schema.rb", File.dirname(__FILE__))
65
- rescue LoadError
66
- end
67
- end
68
-
69
- # Load domain models for this example.
70
- Dir["#{path}/**/*.rb"].each do |model|
71
- require File.expand_path(model, File.dirname(__FILE__))
72
- end
73
-
74
- # Skip empty domain models.
75
- next if ActiveRecord::Base.descendants.empty?
76
-
77
- puts "#{ActiveRecord::Base.descendants.length} models"
78
- [:simple, :advanced].each do |notation|
79
- filename = File.expand_path("examples/#{name}#{notation != :simple ? "-#{notation}" : ""}", File.dirname(__FILE__))
80
-
81
- default_options = { :notation => notation, :filename => filename, :attributes => [:regular],
82
- :title => name.classify + " domain model" }
83
-
84
- specific_options = eval((File.read("#{path}/options.rb") rescue "")) || {}
52
+ require File.expand_path("examples/generate", File.dirname(__FILE__))
53
+ end
85
54
 
86
- # Generate ERD.
87
- RailsERD::Diagram::Graphviz.create(default_options.merge(specific_options))
88
- end
89
- ensure
90
- # Completely remove all loaded Active Record models.
91
- ActiveRecord::Base.descendants.each do |model|
92
- Object.send :remove_const, model.name.to_sym rescue nil
93
- end
94
- ActiveRecord::Base.direct_descendants.clear
95
- Arel::Relation.send :class_variable_set, :@@connection_tables_primary_keys, {}
96
- ActiveSupport::Dependencies::Reference.clear!
97
- end
55
+ namespace :examples do
56
+ task :sfdp do
57
+ require File.expand_path("examples/sfdp", File.dirname(__FILE__))
98
58
  end
99
59
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.0
1
+ 0.4.0
data/lib/rails_erd.rb CHANGED
@@ -1,6 +1,18 @@
1
1
  require "active_support/ordered_options"
2
2
  require "rails_erd/railtie" if defined? Rails
3
3
 
4
+ # Welcome to the API documentation of Rails ERD. If you wish to extend or
5
+ # customise the output that is generated by Rails ERD, you have come to the
6
+ # right place.
7
+ #
8
+ # == Creating custom output
9
+ #
10
+ # If you want to create your own kind of diagrams, or some other output, a
11
+ # good starting point is the RailsERD::Diagram class. It can serve as the base
12
+ # of your output generation code.
13
+ #
14
+ # == Options
15
+ #
4
16
  # Rails ERD provides several options that allow you to customise the
5
17
  # generation of the diagram and the domain model itself. For an overview of
6
18
  # all options available in Rails ERD, see README.rdoc.
@@ -21,15 +33,30 @@ module RailsERD
21
33
  # RailsERD::Diagram will use these options unless overridden.
22
34
  attr_accessor :options
23
35
  end
36
+
37
+ module Inspectable # @private :nodoc:
38
+ def inspection_attributes(*attributes)
39
+ attribute_inspection = attributes.collect { |attribute|
40
+ " @#{attribute}=\#{[Symbol, String].include?(#{attribute}.class) ? #{attribute}.inspect : #{attribute}}"
41
+ }.join
42
+ class_eval <<-RUBY
43
+ def inspect
44
+ "#<\#{self.class}:0x%.14x#{attribute_inspection}>" % (object_id << 1)
45
+ end
46
+ RUBY
47
+ end
48
+ end
24
49
 
25
50
  self.options = ActiveSupport::OrderedOptions[
26
- :attributes, :regular,
51
+ :attributes, :content,
27
52
  :disconnected, true,
28
53
  :filename, "ERD",
29
54
  :filetype, :pdf,
30
55
  :indirect, true,
56
+ :inheritance, false,
31
57
  :notation, :simple,
32
58
  :orientation, :horizontal,
59
+ :polymorphism, false,
33
60
  :warn, true,
34
61
  :title, true
35
62
  ]
@@ -12,7 +12,9 @@ module RailsERD
12
12
  # require "rails_erd/diagram"
13
13
  #
14
14
  # class YumlDiagram < RailsERD::Diagram
15
- # def process_relationship(relationship)
15
+ # setup { @edges = [] }
16
+ #
17
+ # each_relationship do |relationship|
16
18
  # return if relationship.indirect?
17
19
  #
18
20
  # arrow = case
@@ -21,12 +23,10 @@ module RailsERD
21
23
  # when relationship.many_to_many? then "*-*>"
22
24
  # end
23
25
  #
24
- # (@edges ||= []) << "[#{relationship.source}] #{arrow} [#{relationship.destination}]"
26
+ # @edges << "[#{relationship.source}] #{arrow} [#{relationship.destination}]"
25
27
  # end
26
28
  #
27
- # def save
28
- # instructions * "\n"
29
- # end
29
+ # save { @edges * "\n" }
30
30
  # end
31
31
  #
32
32
  # Then, to generate the diagram (example based on the domain model of Gemcutter):
@@ -51,12 +51,17 @@ module RailsERD
51
51
  # diagram generator inheriting from this class.
52
52
  #
53
53
  # attributes:: Selects which attributes to display. Can be any combination of
54
- # +:regular+, +:primary_keys+, +:foreign_keys+, or +:timestamps+.
54
+ # +:content+, +:primary_keys+, +:foreign_keys+, +:timestamps+, or
55
+ # +:inheritance+.
55
56
  # disconnected:: Set to +false+ to exclude entities that are not connected to other
56
57
  # entities. Defaults to +false+.
57
58
  # indirect:: Set to +false+ to exclude relationships that are indirect.
58
59
  # Indirect relationships are defined in Active Record with
59
60
  # <tt>has_many :through</tt> associations.
61
+ # inheritance:: Set to +true+ to include specializations, which correspond to
62
+ # Rails single table inheritance.
63
+ # polymorphism:: Set to +true+ to include generalizations, which correspond to
64
+ # Rails polymorphic associations.
60
65
  # warn:: When set to +false+, no warnings are printed to the
61
66
  # command line while processing the domain model. Defaults
62
67
  # to +true+.
@@ -68,6 +73,34 @@ module RailsERD
68
73
  def create(options = {})
69
74
  new(Domain.generate(options), options).create
70
75
  end
76
+
77
+ protected
78
+
79
+ def setup(&block)
80
+ callbacks[:setup] = block
81
+ end
82
+
83
+ def each_entity(&block)
84
+ callbacks[:each_entity] = block
85
+ end
86
+
87
+ def each_relationship(&block)
88
+ callbacks[:each_relationship] = block
89
+ end
90
+
91
+ def each_specialization(&block)
92
+ callbacks[:each_specialization] = block
93
+ end
94
+
95
+ def save(&block)
96
+ callbacks[:save] = block
97
+ end
98
+
99
+ private
100
+
101
+ def callbacks
102
+ @callbacks ||= Hash.new { proc {} }
103
+ end
71
104
  end
72
105
 
73
106
  # The options that are used to create this diagram.
@@ -90,41 +123,35 @@ module RailsERD
90
123
  # Generates the diagram, but does not save the output. It is called
91
124
  # internally by Diagram#create.
92
125
  def generate
126
+ instance_eval &callbacks[:setup]
127
+
93
128
  filtered_entities.each do |entity|
94
- process_entity entity, filtered_attributes(entity)
129
+ instance_exec entity, filtered_attributes(entity), &callbacks[:each_entity]
95
130
  end
96
131
 
97
- filtered_relationships.each do |relationship|
98
- process_relationship relationship
132
+ filtered_specializations.each do |specialization|
133
+ instance_exec specialization, &callbacks[:each_specialization]
99
134
  end
100
- end
101
-
102
- # Saves the diagram. Can be overridden in subclasses to write to an output
103
- # file. It is called internally by Diagram#create.
104
- def save
105
- end
106
-
107
- protected
108
135
 
109
- # Process a given entity and its attributes. This method should be implemented
110
- # by subclasses. It is intended to add a representation of the entity to
111
- # the diagram. This method will be called once for each entity that should
112
- # be displayed, typically in alphabetic order.
113
- def process_entity(entity, attributes)
136
+ filtered_relationships.each do |relationship|
137
+ instance_exec relationship, &callbacks[:each_relationship]
138
+ end
114
139
  end
115
140
 
116
- # Process a given relationship. This method should be implemented by
117
- # subclasses. It should add a representation of the relationship to
118
- # the diagram. This method will be called once for eacn relationship
119
- # that should be displayed.
120
- def process_relationship(relationship)
141
+ def save
142
+ instance_eval &callbacks[:save]
121
143
  end
122
144
 
123
145
  private
124
146
 
147
+ def callbacks
148
+ @callbacks ||= self.class.send(:callbacks)
149
+ end
150
+
125
151
  def filtered_entities
126
152
  @domain.entities.reject { |entity|
127
- entity.descendant? or
153
+ !options.inheritance && entity.specialized? or
154
+ !options.polymorphism && entity.generalized? or
128
155
  !options.disconnected && entity.disconnected?
129
156
  }.compact.tap do |entities|
130
157
  raise "No entities found; create your models first!" if entities.empty?
@@ -133,19 +160,25 @@ module RailsERD
133
160
 
134
161
  def filtered_relationships
135
162
  @domain.relationships.reject { |relationship|
136
- relationship.source.descendant? or
137
- relationship.destination.descendant? or
138
163
  !options.indirect && relationship.indirect?
139
164
  }
140
165
  end
141
166
 
167
+ def filtered_specializations
168
+ @domain.specializations.reject { |specialization|
169
+ !options.inheritance && specialization.inheritance? or
170
+ !options.polymorphism && specialization.polymorphic?
171
+ }
172
+ end
173
+
142
174
  def filtered_attributes(entity)
143
- entity.attributes.select { |attribute|
175
+ entity.attributes.reject { |attribute|
144
176
  # Select attributes that satisfy the conditions in the :attributes option.
145
- options.attributes and [*options.attributes].any? { |type| attribute.send(:"#{type.to_s.chomp('s')}?") }
177
+ !options.attributes or entity.specialized? or
178
+ [*options.attributes].none? { |type| attribute.send(:"#{type.to_s.chomp('s')}?") }
146
179
  }
147
180
  end
148
-
181
+
149
182
  def warn(message)
150
183
  puts "Warning: #{message}" if options.warn
151
184
  end