rails-erd 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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