rails-erd 1.4.7 → 1.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +19 -11
- data/lib/generators/erd/templates/auto_generate_diagram.rake +1 -1
- data/lib/rails_erd.rb +13 -1
- data/lib/rails_erd/cli.rb +36 -3
- data/lib/rails_erd/config.rb +10 -4
- data/lib/rails_erd/diagram.rb +30 -3
- data/lib/rails_erd/diagram/graphviz.rb +32 -5
- data/lib/rails_erd/diagram/templates/node.html.erb +2 -2
- data/lib/rails_erd/diagram/templates/node.record.erb +2 -2
- data/lib/rails_erd/domain.rb +19 -3
- data/lib/rails_erd/domain/entity.rb +4 -0
- data/lib/rails_erd/domain/relationship.rb +1 -2
- data/lib/rails_erd/domain/specialization.rb +1 -1
- data/lib/rails_erd/tasks.rake +22 -2
- data/lib/rails_erd/version.rb +1 -1
- data/test/test_helper.rb +85 -10
- data/test/unit/attribute_test.rb +20 -19
- data/test/unit/config_test.rb +23 -0
- data/test/unit/diagram_test.rb +9 -0
- data/test/unit/domain_test.rb +27 -10
- data/test/unit/entity_test.rb +26 -0
- data/test/unit/graphviz_test.rb +39 -19
- data/test/unit/rake_task_test.rb +22 -2
- metadata +42 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16bfef1b79087e3017df7723bef8cf286df5a812
|
4
|
+
data.tar.gz: 7162f90b6dce224a0ec89693e7d307fbc15a9b45
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 34e2582512bfe6313f626d4de969eebc328415c97456b1cd151df3e19d75e55cc0b65a9ded7ba0c363d3708072d001266d96657e2a8669ee6a7c4ca7b0cc3ee0
|
7
|
+
data.tar.gz: 16ddf4ba4c7d640a4c139905fe6f238f76c70cf20712c467ebb9bf9075f0ee6603c0c532114b86bd0e66f7d275f5ae0200802e91c8978a469c6e39208dd3c709
|
data/README.md
CHANGED
@@ -2,11 +2,11 @@ Rails ERD - Generate Entity-Relationship Diagrams for Rails applications
|
|
2
2
|
========================================================================
|
3
3
|
[![Build Status](https://travis-ci.org/voormedia/rails-erd.svg?branch=master)](https://travis-ci.org/voormedia/rails-erd) [![Code Climate](https://codeclimate.com/github/voormedia/rails-erd/badges/gpa.svg)](https://codeclimate.com/github/voormedia/rails-erd)
|
4
4
|
|
5
|
-
[Rails ERD](
|
5
|
+
[Rails ERD](https://voormedia.github.io/rails-erd/) is a gem that allows you to easily generate a diagram based on your application's Active Record models. The diagram gives an overview of how your models are related. Having a diagram that describes your models is perfect documentation for your application.
|
6
6
|
|
7
7
|
The second goal of Rails ERD is to provide you with a tool to inspect your application's domain model. If you don't like the default output, it is very easy to use the API to build your own diagrams.
|
8
8
|
|
9
|
-
Rails ERD was created specifically for Rails and works on versions 3.0-
|
9
|
+
Rails ERD was created specifically for Rails and works on versions 3.0-5.0. It uses Active Record's built-in reflection capabilities to figure out how your models are associated.
|
10
10
|
|
11
11
|
|
12
12
|
Preview
|
@@ -14,34 +14,34 @@ Preview
|
|
14
14
|
|
15
15
|
Here's an example entity-relationship diagram that was generated by Rails ERD:
|
16
16
|
|
17
|
-
![Entity-Relationship Diagram](
|
17
|
+
![Entity-Relationship Diagram](https://voormedia.github.io/rails-erd/images/entity-relationship-diagram.png)
|
18
18
|
|
19
|
-
Browse the [gallery](
|
19
|
+
Browse the [gallery](https://voormedia.github.io/rails-erd/gallery.html) for more example diagrams.
|
20
20
|
|
21
21
|
|
22
22
|
Requirements
|
23
23
|
---------------
|
24
24
|
|
25
25
|
* Ruby 1.9.3+
|
26
|
-
* ActiveRecord 3.x
|
26
|
+
* ActiveRecord 3.x - 5.0.x
|
27
27
|
|
28
28
|
Getting started
|
29
29
|
---------------
|
30
30
|
|
31
|
-
See the [installation instructions](
|
31
|
+
See the [installation instructions](https://voormedia.github.io/rails-erd/install.html) for a complete description of how to install Rails ERD. Here's a summary:
|
32
32
|
|
33
|
-
* Install Graphviz 2.22+ ([how?](
|
33
|
+
* Install Graphviz 2.22+ ([how?](https://voormedia.github.io/rails-erd/install.html)). On macOS with Homebrew run `brew install graphviz`.
|
34
34
|
|
35
|
-
* Add <tt>gem
|
35
|
+
* Add <tt>gem 'rails-erd', group: :development</tt> to your application's Gemfile
|
36
36
|
|
37
37
|
* Run <tt>bundle exec erd</tt>
|
38
38
|
|
39
39
|
### Configuration
|
40
40
|
|
41
41
|
|
42
|
-
Rails ERD has the ability to be configured via the command line or through the use of a YAML file with configuration options set. It will look for this file first at `~/.erdconfig` and then `./.erdconfig` (which will override any settings in `~/.erdconfig`). The format of the file is as follows (shown here with the default settings used if no `.erdconfig` is found). More information on [customization options](
|
42
|
+
Rails ERD has the ability to be configured via the command line or through the use of a YAML file with configuration options set. It will look for this file first at `~/.erdconfig` and then `./.erdconfig` (which will override any settings in `~/.erdconfig`). The format of the file is as follows (shown here with the default settings used if no `.erdconfig` is found). More information on [customization options](https://voormedia.github.io/rails-erd/customise.html) can be found in Rails ERD's project documentation.
|
43
43
|
|
44
|
-
```
|
44
|
+
```yaml
|
45
45
|
attributes:
|
46
46
|
- content
|
47
47
|
- foreign_key
|
@@ -60,14 +60,22 @@ warn: true
|
|
60
60
|
title: sample title
|
61
61
|
exclude: null
|
62
62
|
only: null
|
63
|
+
only_recursion_depth: null
|
63
64
|
prepend_primary: false
|
65
|
+
cluster: false
|
66
|
+
splines: spline
|
64
67
|
```
|
65
68
|
|
69
|
+
Auto generation
|
70
|
+
---------------
|
71
|
+
|
72
|
+
* Run <tt>bundle exec rails g erd:install</tt>
|
73
|
+
* Run <tt>bundle exec rails db:migrate</tt>, then the diagram is generated
|
66
74
|
|
67
75
|
Learn more
|
68
76
|
----------
|
69
77
|
|
70
|
-
More information can be found on [Rails ERD's project homepage](
|
78
|
+
More information can be found on [Rails ERD's project homepage](https://voormedia.github.io/rails-erd/).
|
71
79
|
|
72
80
|
If you wish to extend or customise Rails ERD, take a look at the [API documentation](http://rubydoc.info/github/voormedia/rails-erd/frames).
|
73
81
|
|
data/lib/rails_erd.rb
CHANGED
@@ -51,9 +51,21 @@ module RailsERD
|
|
51
51
|
:title, true,
|
52
52
|
:exclude, nil,
|
53
53
|
:only, nil,
|
54
|
-
:
|
54
|
+
:only_recursion_depth, nil,
|
55
|
+
:prepend_primary, false,
|
56
|
+
:cluster, false,
|
55
57
|
]
|
56
58
|
end
|
59
|
+
|
60
|
+
def loaded_tasks=(val); @loaded_tasks = val; end
|
61
|
+
def loaded_tasks; return @loaded_tasks; end
|
62
|
+
|
63
|
+
def load_tasks
|
64
|
+
return if(self.loaded_tasks)
|
65
|
+
self.loaded_tasks = true
|
66
|
+
|
67
|
+
Dir[File.join(File.dirname(__FILE__), 'tasks', '**/*.rake')].each { |rake| load rake }
|
68
|
+
end
|
57
69
|
end
|
58
70
|
|
59
71
|
module Inspectable # @private :nodoc:
|
data/lib/rails_erd/cli.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require "rails_erd"
|
1
2
|
require "choice"
|
2
3
|
|
3
4
|
Choice.options do
|
@@ -49,6 +50,11 @@ Choice.options do
|
|
49
50
|
desc "Filter to only include listed models in diagram."
|
50
51
|
end
|
51
52
|
|
53
|
+
option :only_recursion_depth do
|
54
|
+
long "--only_recursion_depth=INTEGER"
|
55
|
+
desc "Recurses into relations specified by --only upto a depth N."
|
56
|
+
end
|
57
|
+
|
52
58
|
option :exclude do
|
53
59
|
long "--exclude"
|
54
60
|
desc "Filter to exclude listed models in diagram."
|
@@ -64,6 +70,16 @@ Choice.options do
|
|
64
70
|
desc "Ensure primary key is at start of attribute list"
|
65
71
|
end
|
66
72
|
|
73
|
+
option :cluster do
|
74
|
+
long "--cluster"
|
75
|
+
desc "Display models in subgraphs based on their namespace."
|
76
|
+
end
|
77
|
+
|
78
|
+
option :splines do
|
79
|
+
long "--splines=SPLINE_TYPE"
|
80
|
+
desc "Control how edges are represented. See http://www.graphviz.org/doc/info/attrs.html#d:splines for values."
|
81
|
+
end
|
82
|
+
|
67
83
|
separator ""
|
68
84
|
separator "Output options:"
|
69
85
|
|
@@ -110,6 +126,12 @@ Choice.options do
|
|
110
126
|
exit
|
111
127
|
end
|
112
128
|
end
|
129
|
+
|
130
|
+
option :config_file do
|
131
|
+
short "-c"
|
132
|
+
long "--config=FILENAME"
|
133
|
+
desc "Configuration file to use"
|
134
|
+
end
|
113
135
|
end
|
114
136
|
|
115
137
|
module RailsERD
|
@@ -128,6 +150,9 @@ module RailsERD
|
|
128
150
|
opts[key.to_sym] = value
|
129
151
|
end
|
130
152
|
end
|
153
|
+
if options[:config_file] && options[:config_file] != ''
|
154
|
+
RailsERD.options = RailsERD.default_options.merge(Config.load(options[:config_file]))
|
155
|
+
end
|
131
156
|
new(path, options).start
|
132
157
|
end
|
133
158
|
end
|
@@ -149,9 +174,17 @@ module RailsERD
|
|
149
174
|
|
150
175
|
def load_application
|
151
176
|
$stderr.puts "Loading application in '#{File.basename(path)}'..."
|
152
|
-
|
153
|
-
|
154
|
-
|
177
|
+
begin
|
178
|
+
environment_path = "#{path}/config/environment.rb"
|
179
|
+
require environment_path
|
180
|
+
rescue ::LoadError
|
181
|
+
puts "Please create a file in '#{environment_path}' that loads your application environment."
|
182
|
+
raise
|
183
|
+
end
|
184
|
+
if defined? Rails
|
185
|
+
Rails.application.eager_load!
|
186
|
+
Rails.application.config.eager_load_namespaces.each(&:eager_load!) if Rails.application.config.respond_to?(:eager_load_namespaces)
|
187
|
+
end
|
155
188
|
rescue TypeError
|
156
189
|
end
|
157
190
|
|
data/lib/rails_erd/config.rb
CHANGED
@@ -7,17 +7,21 @@ module RailsERD
|
|
7
7
|
|
8
8
|
attr_reader :options
|
9
9
|
|
10
|
-
def self.load
|
11
|
-
new.load
|
10
|
+
def self.load(extra_config_file=nil)
|
11
|
+
new.load extra_config_file
|
12
12
|
end
|
13
13
|
|
14
14
|
def initialize
|
15
15
|
@options = {}
|
16
16
|
end
|
17
17
|
|
18
|
-
def load
|
18
|
+
def load(extra_config_file=nil)
|
19
19
|
load_file(USER_WIDE_CONFIG_FILE)
|
20
20
|
load_file(CURRENT_CONFIG_FILE)
|
21
|
+
if extra_config_file
|
22
|
+
extra_config_path = File.expand_path(extra_config_file, Dir.pwd)
|
23
|
+
load_file(extra_config_path) if File.exist?(extra_config_path)
|
24
|
+
end
|
21
25
|
|
22
26
|
@options
|
23
27
|
end
|
@@ -63,8 +67,10 @@ module RailsERD
|
|
63
67
|
# [<string>]
|
64
68
|
when :only, :exclude
|
65
69
|
Array(value).join(",").split(",").map { |v| v.strip }
|
70
|
+
|
66
71
|
# true | false
|
67
|
-
when :disconnected, :indirect, :inheritance, :markup, :polymorphism,
|
72
|
+
when :disconnected, :indirect, :inheritance, :markup, :polymorphism,
|
73
|
+
:warn, :cluster
|
68
74
|
!!value
|
69
75
|
|
70
76
|
# nil | <string>
|
data/lib/rails_erd/diagram.rb
CHANGED
@@ -7,7 +7,7 @@ module RailsERD
|
|
7
7
|
# and (optionally) +save+.
|
8
8
|
#
|
9
9
|
# As an example, a diagram class that generates code that can be used with
|
10
|
-
# yUML (
|
10
|
+
# yUML (https://yuml.me) can be as simple as:
|
11
11
|
#
|
12
12
|
# require "rails_erd/diagram"
|
13
13
|
#
|
@@ -124,6 +124,13 @@ module RailsERD
|
|
124
124
|
# internally by Diagram#create.
|
125
125
|
def generate
|
126
126
|
instance_eval(&callbacks[:setup])
|
127
|
+
if options.only_recursion_depth.present?
|
128
|
+
depth = options.only_recursion_depth.to_s.to_i
|
129
|
+
options[:only].dup.each do |class_name|
|
130
|
+
options[:only]+= recurse_into_relationships(@domain.entity_by_name(class_name), depth)
|
131
|
+
end
|
132
|
+
options[:only].uniq!
|
133
|
+
end
|
127
134
|
|
128
135
|
filtered_entities.each do |entity|
|
129
136
|
instance_exec entity, filtered_attributes(entity), &callbacks[:each_entity]
|
@@ -138,6 +145,26 @@ module RailsERD
|
|
138
145
|
end
|
139
146
|
end
|
140
147
|
|
148
|
+
def recurse_into_relationships(entity, max_level, current_level = 0)
|
149
|
+
return [] unless entity
|
150
|
+
return [] if max_level == current_level
|
151
|
+
|
152
|
+
relationships = entity.relationships.reject{|r| r.indirect? || r.recursive?}
|
153
|
+
|
154
|
+
relationships.map do |relationship|
|
155
|
+
other_entitiy = if relationship.source == entity
|
156
|
+
relationship.destination
|
157
|
+
else
|
158
|
+
relationship.source
|
159
|
+
end
|
160
|
+
if other_entitiy and !other_entitiy.generalized?
|
161
|
+
[other_entitiy.name] + recurse_into_relationships(other_entitiy, max_level, current_level + 1)
|
162
|
+
else
|
163
|
+
[]
|
164
|
+
end
|
165
|
+
end.flatten.uniq
|
166
|
+
end
|
167
|
+
|
141
168
|
def save
|
142
169
|
instance_eval(&callbacks[:save])
|
143
170
|
end
|
@@ -150,8 +177,8 @@ module RailsERD
|
|
150
177
|
|
151
178
|
def filtered_entities
|
152
179
|
@domain.entities.reject { |entity|
|
153
|
-
options.exclude.present? &&
|
154
|
-
options
|
180
|
+
options.exclude.present? && [options.exclude].flatten.map(&:to_sym).include?(entity.name.to_sym) or
|
181
|
+
options[:only].present? && entity.model && ![options[:only]].flatten.map(&:to_sym).include?(entity.name.to_sym) or
|
155
182
|
!options.inheritance && entity.specialized? or
|
156
183
|
!options.polymorphism && entity.generalized? or
|
157
184
|
!options.disconnected && entity.disconnected?
|
@@ -62,7 +62,8 @@ module RailsERD
|
|
62
62
|
concentrate: true,
|
63
63
|
labelloc: :t,
|
64
64
|
fontsize: 13,
|
65
|
-
fontname: FONTS[:bold]
|
65
|
+
fontname: FONTS[:bold],
|
66
|
+
splines: 'spline'
|
66
67
|
}
|
67
68
|
|
68
69
|
# Default node attributes.
|
@@ -85,6 +86,11 @@ module RailsERD
|
|
85
86
|
labeldistance: 1.8,
|
86
87
|
}
|
87
88
|
|
89
|
+
# Default cluster attributes.
|
90
|
+
CLUSTER_ATTRIBUTES = {
|
91
|
+
margin: "10,10"
|
92
|
+
}
|
93
|
+
|
88
94
|
module Simple
|
89
95
|
def entity_style(entity, attributes)
|
90
96
|
{}.tap do |options|
|
@@ -188,6 +194,9 @@ module RailsERD
|
|
188
194
|
# Title of the graph itself.
|
189
195
|
graph[:label] = "#{title}\\n\\n" if title
|
190
196
|
|
197
|
+
# Style of splines
|
198
|
+
graph[:splines] = options.splines unless options.splines.nil?
|
199
|
+
|
191
200
|
# Setup notation options.
|
192
201
|
extend self.class.const_get(options.notation.to_s.capitalize.to_sym)
|
193
202
|
end
|
@@ -210,7 +219,16 @@ module RailsERD
|
|
210
219
|
end
|
211
220
|
|
212
221
|
each_entity do |entity, attributes|
|
213
|
-
|
222
|
+
if options[:cluster] && entity.namespace
|
223
|
+
cluster_name = "cluster_#{entity.namespace}"
|
224
|
+
cluster_options = CLUSTER_ATTRIBUTES.merge(label: entity.namespace)
|
225
|
+
cluster = graph.get_graph(cluster_name) ||
|
226
|
+
graph.add_graph(cluster_name, cluster_options)
|
227
|
+
|
228
|
+
draw_cluster_node cluster, entity.name, entity_options(entity, attributes)
|
229
|
+
else
|
230
|
+
draw_node entity.name, entity_options(entity, attributes)
|
231
|
+
end
|
214
232
|
end
|
215
233
|
|
216
234
|
each_specialization do |specialization|
|
@@ -233,15 +251,19 @@ module RailsERD
|
|
233
251
|
private
|
234
252
|
|
235
253
|
def node_exists?(name)
|
236
|
-
!!graph.
|
254
|
+
!!graph.search_node(escape_name(name))
|
237
255
|
end
|
238
256
|
|
239
257
|
def draw_node(name, options)
|
240
258
|
graph.add_nodes escape_name(name), options
|
241
259
|
end
|
242
260
|
|
261
|
+
def draw_cluster_node(cluster, name, options)
|
262
|
+
cluster.add_nodes escape_name(name), options
|
263
|
+
end
|
264
|
+
|
243
265
|
def draw_edge(from, to, options)
|
244
|
-
graph.add_edges graph.
|
266
|
+
graph.add_edges graph.search_node(escape_name(from)), graph.search_node(escape_name(to)), options if node_exists?(from) and node_exists?(to)
|
245
267
|
end
|
246
268
|
|
247
269
|
def escape_name(name)
|
@@ -288,7 +310,12 @@ module RailsERD
|
|
288
310
|
end
|
289
311
|
|
290
312
|
def read_template(type)
|
291
|
-
|
313
|
+
template_text = File.read(File.expand_path("templates/#{NODE_LABEL_TEMPLATES[type]}", File.dirname(__FILE__)))
|
314
|
+
if ERB.instance_method(:initialize).parameters.assoc(:key) # Ruby 2.6+
|
315
|
+
ERB.new(template_text, trim_mode: "<>")
|
316
|
+
else
|
317
|
+
ERB.new(template_text, nil, "<>")
|
318
|
+
end
|
292
319
|
end
|
293
320
|
end
|
294
321
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<% if options.orientation == :
|
1
|
+
<% if options.orientation == :horizontal %>{<% end %>
|
2
2
|
<table border="0" align="center" cellspacing="0.5" cellpadding="0" width="<%= NODE_WIDTH + 4 %>">
|
3
3
|
<tr><td align="center" valign="bottom" width="<%= NODE_WIDTH %>"><font face="<%= FONTS[:bold] %>" point-size="11"><%= entity.name %></font></td></tr>
|
4
4
|
</table>
|
@@ -11,4 +11,4 @@
|
|
11
11
|
</table>
|
12
12
|
<% else %>
|
13
13
|
<% end %>
|
14
|
-
<% if options.orientation == :
|
14
|
+
<% if options.orientation == :horizontal %>}<% end %>
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<% if options.orientation == :
|
1
|
+
<% if options.orientation == :horizontal %>{<% end %><%= entity.name %><% if attributes.any? %>
|
2
2
|
|<% attributes.each do |attribute| %><%=
|
3
3
|
attribute %> (<%= attribute.type_description %>)
|
4
|
-
<% end %><% end %><% if options.orientation == :
|
4
|
+
<% end %><% end %><% if options.orientation == :horizontal %>}<% end %>
|
data/lib/rails_erd/domain.rb
CHANGED
@@ -49,7 +49,13 @@ module RailsERD
|
|
49
49
|
# Returns the domain model name, which is the name of your Rails
|
50
50
|
# application or +nil+ outside of Rails.
|
51
51
|
def name
|
52
|
-
defined?
|
52
|
+
return unless defined?(Rails) && Rails.application
|
53
|
+
|
54
|
+
if Rails.application.class.respond_to?(:module_parent)
|
55
|
+
Rails.application.class.module_parent.name
|
56
|
+
else
|
57
|
+
Rails.application.class.parent.name
|
58
|
+
end
|
53
59
|
end
|
54
60
|
|
55
61
|
# Returns all entities of your domain model.
|
@@ -140,8 +146,7 @@ module RailsERD
|
|
140
146
|
association.check_validity!
|
141
147
|
|
142
148
|
if association.options[:polymorphic]
|
143
|
-
|
144
|
-
entity_by_name(entity_name) or raise "polymorphic interface #{entity_name} does not exist"
|
149
|
+
check_polymorphic_association_validity(association)
|
145
150
|
else
|
146
151
|
entity_name = association.klass.name # Raises NameError if the associated class cannot be found.
|
147
152
|
entity_by_name(entity_name) or raise "model #{entity_name} exists, but is not included in domain"
|
@@ -150,6 +155,17 @@ module RailsERD
|
|
150
155
|
warn "Ignoring invalid association #{association_description(association)} (#{e.message})"
|
151
156
|
end
|
152
157
|
|
158
|
+
def check_polymorphic_association_validity(association)
|
159
|
+
entity_name = association.class_name
|
160
|
+
entity = entity_by_name(entity_name)
|
161
|
+
|
162
|
+
if entity || (entity && entity.generalized?)
|
163
|
+
return entity
|
164
|
+
else
|
165
|
+
raise("polymorphic interface #{entity_name} does not exist")
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
153
169
|
def association_description(association)
|
154
170
|
"#{association.name.inspect} on #{association.active_record}"
|
155
171
|
end
|
@@ -21,8 +21,7 @@ module RailsERD
|
|
21
21
|
private
|
22
22
|
|
23
23
|
def association_identity(association)
|
24
|
-
|
25
|
-
Set[identifier, association_owner(association), association_target(association)]
|
24
|
+
Set[association_owner(association), association_target(association)]
|
26
25
|
end
|
27
26
|
|
28
27
|
def association_identifier(association)
|
@@ -30,7 +30,7 @@ module RailsERD
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def abstract_from_models(domain, models)
|
33
|
-
models.select(&:abstract_class?).collect(&:
|
33
|
+
models.select(&:abstract_class?).collect(&:direct_descendants).flatten.collect { |model|
|
34
34
|
new(domain, domain.entity_by_name(model.superclass.name), domain.entity_by_name(model.name))
|
35
35
|
}
|
36
36
|
end
|
data/lib/rails_erd/tasks.rake
CHANGED
@@ -1,15 +1,31 @@
|
|
1
|
+
require 'graphviz/utils'
|
2
|
+
|
1
3
|
def say(message)
|
2
4
|
puts message unless Rake.application.options.silent
|
3
5
|
end
|
4
6
|
|
5
7
|
namespace :erd do
|
8
|
+
task :check_dependencies do
|
9
|
+
include GraphViz::Utils
|
10
|
+
unless find_executable("dot", nil)
|
11
|
+
raise "Unable to find GraphViz's \"dot\" executable. Please " \
|
12
|
+
"visit https://voormedia.github.io/rails-erd/install.html for installation instructions."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
6
16
|
task :options do
|
7
17
|
(RailsERD.options.keys.map(&:to_s) & ENV.keys).each do |option|
|
8
18
|
RailsERD.options[option.to_sym] = case ENV[option]
|
9
19
|
when "true", "yes" then true
|
10
20
|
when "false", "no" then false
|
11
21
|
when /,/ then ENV[option].split(/\s*,\s*/)
|
12
|
-
|
22
|
+
when /^\d+$/ then ENV[option].to_i
|
23
|
+
else
|
24
|
+
if option == 'only'
|
25
|
+
[ENV[option]]
|
26
|
+
else
|
27
|
+
ENV[option].to_sym
|
28
|
+
end
|
13
29
|
end
|
14
30
|
end
|
15
31
|
end
|
@@ -21,6 +37,10 @@ namespace :erd do
|
|
21
37
|
say "Loading code in search of Active Record models..."
|
22
38
|
begin
|
23
39
|
Rails.application.eager_load!
|
40
|
+
|
41
|
+
if Rails.application.respond_to?(:config) && !Rails.application.config.nil?
|
42
|
+
Rails.application.config.eager_load_namespaces.each(&:eager_load!) if Rails.application.config.respond_to?(:eager_load_namespaces)
|
43
|
+
end
|
24
44
|
rescue Exception => err
|
25
45
|
if Rake.application.options.trace
|
26
46
|
raise
|
@@ -34,7 +54,7 @@ namespace :erd do
|
|
34
54
|
raise "Active Record was not loaded." unless defined? ActiveRecord
|
35
55
|
end
|
36
56
|
|
37
|
-
task :generate => [:options, :load_models] do
|
57
|
+
task :generate => [:check_dependencies, :options, :load_models] do
|
38
58
|
say "Generating Entity-Relationship Diagram for #{ActiveRecord::Base.descendants.length} models..."
|
39
59
|
|
40
60
|
require "rails_erd/diagram/graphviz"
|
data/lib/rails_erd/version.rb
CHANGED
data/test/test_helper.rb
CHANGED
@@ -1,15 +1,12 @@
|
|
1
1
|
require "rubygems"
|
2
2
|
require "bundler/setup"
|
3
|
+
require 'pry'
|
4
|
+
require 'pry-nav'
|
3
5
|
|
4
6
|
require "active_record"
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
require 'mocha/mini_test'
|
9
|
-
else
|
10
|
-
require "test/unit"
|
11
|
-
require 'mocha/test_unit'
|
12
|
-
end
|
8
|
+
require "minitest/autorun"
|
9
|
+
require 'mocha/minitest'
|
13
10
|
|
14
11
|
require "rails_erd/domain"
|
15
12
|
|
@@ -19,6 +16,14 @@ if ActiveSupport::TestCase.respond_to?(:test_order=)
|
|
19
16
|
ActiveSupport::TestCase.test_order = :random
|
20
17
|
end
|
21
18
|
|
19
|
+
# Patch to make Rails 6.1 work.
|
20
|
+
module Kernel
|
21
|
+
# class_eval on an object acts like singleton_class.class_eval.
|
22
|
+
def class_eval(*args, &block)
|
23
|
+
singleton_class.class_eval(*args, &block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
22
27
|
class ActiveSupport::TestCase
|
23
28
|
include RailsERD
|
24
29
|
|
@@ -48,10 +53,34 @@ class ActiveSupport::TestCase
|
|
48
53
|
ActiveRecord::Base.clear_cache!
|
49
54
|
end
|
50
55
|
|
56
|
+
def create_module_model(full_name,*args,&block)
|
57
|
+
superklass = args.first.kind_of?(Class) ? args.shift : ActiveRecord::Base
|
58
|
+
|
59
|
+
names = full_name.split('::')
|
60
|
+
|
61
|
+
parent_module = names[0..-1].inject(Object) do |parent,child|
|
62
|
+
parent = parent.const_set(child.to_sym, Module.new)
|
63
|
+
end
|
64
|
+
|
65
|
+
parent_module ||= Object
|
66
|
+
name = names.last
|
67
|
+
|
68
|
+
columns = args.first || {}
|
69
|
+
klass = parent_module.const_set name.to_sym, Class.new(superklass)
|
70
|
+
konstant = parent_module.const_get(name.to_sym)
|
71
|
+
|
72
|
+
if superklass == ActiveRecord::Base || superklass.abstract_class?
|
73
|
+
create_table konstant.table_name, columns, konstant.primary_key rescue nil
|
74
|
+
end
|
75
|
+
klass.class_eval(&block) if block_given?
|
76
|
+
konstant
|
77
|
+
end
|
78
|
+
|
51
79
|
def create_model(name, *args, &block)
|
52
80
|
superklass = args.first.kind_of?(Class) ? args.shift : ActiveRecord::Base
|
53
81
|
columns = args.first || {}
|
54
82
|
klass = Object.const_set name.to_sym, Class.new(superklass)
|
83
|
+
|
55
84
|
if superklass == ActiveRecord::Base || superklass.abstract_class?
|
56
85
|
create_table Object.const_get(name.to_sym).table_name, columns, Object.const_get(name.to_sym).primary_key rescue nil
|
57
86
|
end
|
@@ -143,18 +172,64 @@ class ActiveSupport::TestCase
|
|
143
172
|
RailsERD.options = RailsERD.default_options.merge(Config.load)
|
144
173
|
end
|
145
174
|
|
175
|
+
def name_to_object_symbol_pairs(name)
|
176
|
+
parts = name.to_s.split('::')
|
177
|
+
|
178
|
+
return [] if parts.first == '' || parts.count == 0
|
179
|
+
|
180
|
+
parts[1..-1].inject([[Object, parts.first.to_sym]]) do |pairs,string|
|
181
|
+
last_parent, last_child = pairs.last
|
182
|
+
# Fixes for Rails 6. No idea if this is actually correct as I can't decipher what the heck is going on in this
|
183
|
+
# code.
|
184
|
+
if last_child == :ActiveRecord || last_child == :primary
|
185
|
+
break []
|
186
|
+
end
|
187
|
+
|
188
|
+
break pairs unless last_parent.const_defined?(last_child)
|
189
|
+
|
190
|
+
next_parent = last_parent.const_get(last_child)
|
191
|
+
next_child = string.to_sym
|
192
|
+
pairs << [next_parent, next_child]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def remove_fully_qualified_constant(name)
|
197
|
+
pairs = name_to_object_symbol_pairs(name)
|
198
|
+
pairs.reverse.each do |parent, child|
|
199
|
+
parent.send(:remove_const,child) if parent.const_defined?(child)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
146
203
|
def reset_domain
|
147
204
|
if defined? ActiveRecord
|
148
205
|
ActiveRecord::Base.descendants.each do |model|
|
206
|
+
next if model.name == "ActiveRecord::InternalMetadata"
|
149
207
|
model.reset_column_information
|
150
|
-
|
208
|
+
remove_fully_qualified_constant(model.name)
|
151
209
|
end
|
152
|
-
|
210
|
+
|
211
|
+
tables_and_views.each do |table|
|
153
212
|
ActiveRecord::Base.connection.drop_table table
|
154
213
|
end
|
155
|
-
|
214
|
+
|
215
|
+
if ActiveRecord.version >= Gem::Version.new("6.0.0.rc1")
|
216
|
+
cv = ActiveSupport::DescendantsTracker.class_variable_get(:@@direct_descendants)
|
217
|
+
cv.delete(ActiveRecord::Base)
|
218
|
+
ActiveSupport::DescendantsTracker.class_variable_set(:@@direct_descendants, cv)
|
219
|
+
else
|
220
|
+
ActiveRecord::Base.direct_descendants.clear
|
221
|
+
end
|
222
|
+
|
156
223
|
ActiveSupport::Dependencies::Reference.clear!
|
157
224
|
ActiveRecord::Base.clear_cache!
|
158
225
|
end
|
159
226
|
end
|
227
|
+
|
228
|
+
def tables_and_views
|
229
|
+
if ActiveRecord::VERSION::MAJOR >= 5
|
230
|
+
ActiveRecord::Base.connection.data_sources
|
231
|
+
else
|
232
|
+
ActiveRecord::Base.connection.tables
|
233
|
+
end
|
234
|
+
end
|
160
235
|
end
|
data/test/unit/attribute_test.rb
CHANGED
@@ -3,8 +3,9 @@ require File.expand_path("../test_helper", File.dirname(__FILE__))
|
|
3
3
|
|
4
4
|
class AttributeTest < ActiveSupport::TestCase
|
5
5
|
def with_native_limit(type, new_limit)
|
6
|
-
ActiveRecord::Base.connection.class_eval do
|
7
|
-
|
6
|
+
ActiveRecord::Base.connection.singleton_class.class_eval do
|
7
|
+
undef :native_database_types
|
8
|
+
define_method(:native_database_types) do
|
8
9
|
super().tap do |types|
|
9
10
|
types[type][:limit] = new_limit
|
10
11
|
end
|
@@ -12,8 +13,9 @@ class AttributeTest < ActiveSupport::TestCase
|
|
12
13
|
end
|
13
14
|
yield
|
14
15
|
ensure
|
15
|
-
ActiveRecord::Base.connection.class_eval do
|
16
|
-
|
16
|
+
ActiveRecord::Base.connection.singleton_class.class_eval do
|
17
|
+
undef :native_database_types
|
18
|
+
define_method(:native_database_types) do
|
17
19
|
super()
|
18
20
|
end
|
19
21
|
end
|
@@ -222,14 +224,14 @@ class AttributeTest < ActiveSupport::TestCase
|
|
222
224
|
test "limit should return nil if there is no limit" do
|
223
225
|
create_model "Foo"
|
224
226
|
add_column :foos, :my_txt, :text
|
225
|
-
|
227
|
+
assert_nil create_attribute(Foo, "my_txt").limit
|
226
228
|
end
|
227
229
|
|
228
230
|
test "limit should return nil if equal to standard database limit" do
|
229
231
|
with_native_limit :string, 456 do
|
230
232
|
create_model "Foo"
|
231
233
|
add_column :foos, :my_str, :string, :limit => 456
|
232
|
-
|
234
|
+
assert_nil create_attribute(Foo, "my_str").limit
|
233
235
|
end
|
234
236
|
end
|
235
237
|
|
@@ -250,7 +252,7 @@ class AttributeTest < ActiveSupport::TestCase
|
|
250
252
|
test "limit should return nil for decimal columns if equal to standard database limit" do
|
251
253
|
create_model "Foo"
|
252
254
|
add_column :foos, :num, :decimal
|
253
|
-
|
255
|
+
assert_nil create_attribute(Foo, "num").limit
|
254
256
|
end
|
255
257
|
|
256
258
|
test "limit should return nil if type is unsupported by rails" do
|
@@ -260,19 +262,19 @@ class AttributeTest < ActiveSupport::TestCase
|
|
260
262
|
add_column "foos", "a", "REAL"
|
261
263
|
end
|
262
264
|
end
|
263
|
-
|
265
|
+
assert_nil create_attribute(Foo, "a").limit
|
264
266
|
end
|
265
267
|
|
266
268
|
test "limit should return nil for oddball column types that misuse the limit attribute" do
|
267
|
-
create_model "Business", :location => :integer
|
268
|
-
|
269
|
-
attribute.column.class_eval do
|
270
|
-
define_method :limit do
|
269
|
+
create_model "Business", :location => :integer do
|
270
|
+
define_singleton_method :limit do
|
271
271
|
# https://github.com/voormedia/rails-erd/issues/21
|
272
272
|
{ :srid => 4326, :type => "point", :geographic => true }
|
273
273
|
end
|
274
274
|
end
|
275
|
-
|
275
|
+
|
276
|
+
attribute = create_attribute(Business, "location")
|
277
|
+
assert_nil attribute.limit
|
276
278
|
end
|
277
279
|
|
278
280
|
test "scale should return scale for decimal columns if nonstandard" do
|
@@ -284,7 +286,7 @@ class AttributeTest < ActiveSupport::TestCase
|
|
284
286
|
test "scale should return nil for decimal columns if equal to standard database limit" do
|
285
287
|
create_model "Foo"
|
286
288
|
add_column :foos, :num, :decimal
|
287
|
-
|
289
|
+
assert_nil create_attribute(Foo, "num").scale
|
288
290
|
end
|
289
291
|
|
290
292
|
test "scale should return zero for decimal columns if left to default setting when specifying precision" do
|
@@ -300,17 +302,16 @@ class AttributeTest < ActiveSupport::TestCase
|
|
300
302
|
add_column "foos", "a", "REAL"
|
301
303
|
end
|
302
304
|
end
|
303
|
-
|
305
|
+
assert_nil create_attribute(Foo, "a").scale
|
304
306
|
end
|
305
307
|
|
306
308
|
test "scale should return nil for oddball column types that misuse the scale attribute" do
|
307
|
-
create_model "Kobold", :size => :integer
|
308
|
-
attribute = create_attribute(Kobold, "size")
|
309
|
-
attribute.column.class_eval do
|
309
|
+
create_model "Kobold", :size => :integer do
|
310
310
|
define_method :scale do
|
311
311
|
1..5
|
312
312
|
end
|
313
313
|
end
|
314
|
-
|
314
|
+
attribute = create_attribute(Kobold, "size")
|
315
|
+
assert_nil attribute.scale
|
315
316
|
end
|
316
317
|
end
|
data/test/unit/config_test.rb
CHANGED
@@ -61,6 +61,29 @@ class ConfigTest < ActiveSupport::TestCase
|
|
61
61
|
assert_equal expected_hash, RailsERD::Config.load
|
62
62
|
end
|
63
63
|
|
64
|
+
test "load_config_file should return a hash from the configured config file when a new config file is given as an argument" do
|
65
|
+
set_local_config_file_to("erdconfig.another_example")
|
66
|
+
|
67
|
+
expected_hash = {
|
68
|
+
attributes: [:content, :foreign_key, :inheritance, :false],
|
69
|
+
disconnected: true,
|
70
|
+
filename: "erd",
|
71
|
+
filetype: :pdf,
|
72
|
+
indirect: true,
|
73
|
+
inheritance: false,
|
74
|
+
markup: true,
|
75
|
+
notation: :simple,
|
76
|
+
orientation: "horizontal",
|
77
|
+
polymorphism: false,
|
78
|
+
warn: true,
|
79
|
+
title: "sample title",
|
80
|
+
exclude: [],
|
81
|
+
only: []
|
82
|
+
}
|
83
|
+
|
84
|
+
assert_equal expected_hash, RailsERD::Config.load("test/support_files/erdconfig.example")
|
85
|
+
end
|
86
|
+
|
64
87
|
test "load_config_gile should return a hash from CURRENT_CONFIG_FILE overriding USER_WIDE_CONFIG_FILE when both of them exist." do
|
65
88
|
set_user_config_file_to("erdconfig.example")
|
66
89
|
set_local_config_file_to("erdconfig.another_example")
|
data/test/unit/diagram_test.rb
CHANGED
@@ -136,6 +136,15 @@ class DiagramTest < ActiveSupport::TestCase
|
|
136
136
|
assert_equal [Book], retrieve_entities(:exclude => [:Author, :Editor]).map(&:model)
|
137
137
|
end
|
138
138
|
|
139
|
+
test "generate should filter excluded polymorphic entities" do
|
140
|
+
create_model "Cannon"
|
141
|
+
create_model "Galleon" do
|
142
|
+
has_many :cannons, as: :defensible
|
143
|
+
end
|
144
|
+
assert_equal ["Cannon", "Galleon"], retrieve_entities(polymorphism: true, exclude: :Defensible).map(&:name)
|
145
|
+
end
|
146
|
+
|
147
|
+
|
139
148
|
test "generate should include only specified entity" do
|
140
149
|
create_model "Book"
|
141
150
|
create_model "Author"
|
data/test/unit/domain_test.rb
CHANGED
@@ -127,13 +127,21 @@ class DomainTest < ActiveSupport::TestCase
|
|
127
127
|
end
|
128
128
|
|
129
129
|
test "relationships should count relationship between same models with distinct foreign key seperately" do
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
130
|
+
# TODO: Once we drop Rails 3.2 support, we _should_ be able to drop the
|
131
|
+
# :respond_to? check
|
132
|
+
#
|
133
|
+
if respond_to? :skip
|
134
|
+
skip("multiple edges between the same objects can cause segfaults in some versions of Graphviz")
|
135
|
+
|
136
|
+
create_model "Foo", :bar => :references, :special_bar => :references do
|
137
|
+
belongs_to :bar
|
138
|
+
end
|
139
|
+
create_model "Bar" do
|
140
|
+
has_many :foos, :foreign_key => :special_bar_id
|
141
|
+
end
|
142
|
+
|
143
|
+
assert_equal [Domain::Relationship] * 2, Domain.generate.relationships.collect(&:class)
|
135
144
|
end
|
136
|
-
assert_equal [Domain::Relationship] * 2, Domain.generate.relationships.collect(&:class)
|
137
145
|
end
|
138
146
|
|
139
147
|
test "relationships should use model name first in alphabet as source for many to many relationships" do
|
@@ -186,6 +194,15 @@ class DomainTest < ActiveSupport::TestCase
|
|
186
194
|
assert_equal [Domain::Specialization] * 3, Domain.generate.specializations.collect(&:class)
|
187
195
|
end
|
188
196
|
|
197
|
+
test "specializations should return specializations in domain model once for descendants of abstract class" do
|
198
|
+
create_model "Thing" do
|
199
|
+
self.abstract_class = true
|
200
|
+
end
|
201
|
+
create_model "Beverage", Thing, :type => :string
|
202
|
+
create_model "Beer", Beverage
|
203
|
+
assert_equal [Domain::Specialization], Domain.generate.specializations.collect(&:class)
|
204
|
+
end
|
205
|
+
|
189
206
|
# Erroneous associations ===================================================
|
190
207
|
test "relationships should omit bad has_many associations" do
|
191
208
|
create_model "Foo" do
|
@@ -216,7 +233,7 @@ class DomainTest < ActiveSupport::TestCase
|
|
216
233
|
output = collect_stdout do
|
217
234
|
Domain.generate.relationships
|
218
235
|
end
|
219
|
-
assert_match
|
236
|
+
assert_match(/Ignoring invalid association :flabs on Foo/, output)
|
220
237
|
end
|
221
238
|
|
222
239
|
test "relationships should output a warning when an association to model outside domain is encountered" do
|
@@ -227,7 +244,7 @@ class DomainTest < ActiveSupport::TestCase
|
|
227
244
|
output = collect_stdout do
|
228
245
|
Domain.new([Foo]).relationships
|
229
246
|
end
|
230
|
-
assert_match
|
247
|
+
assert_match(/model Bar exists, but is not included in domain/, output)
|
231
248
|
end
|
232
249
|
|
233
250
|
test "relationships should output a warning when an association to a non existent generalization is encountered" do
|
@@ -240,7 +257,7 @@ class DomainTest < ActiveSupport::TestCase
|
|
240
257
|
output = collect_stdout do
|
241
258
|
Domain.generate.relationships
|
242
259
|
end
|
243
|
-
assert_match
|
260
|
+
assert_match(/polymorphic interface FooBar does not exist/, output)
|
244
261
|
end
|
245
262
|
|
246
263
|
test "relationships should not warn when a bad association is encountered if warnings are disabled" do
|
@@ -264,6 +281,6 @@ class DomainTest < ActiveSupport::TestCase
|
|
264
281
|
output = collect_stdout do
|
265
282
|
Domain.generate.entities
|
266
283
|
end
|
267
|
-
assert_match
|
284
|
+
assert_match(/Ignoring invalid model Foo \(table foos does not exist\)/, output)
|
268
285
|
end
|
269
286
|
end
|
data/test/unit/entity_test.rb
CHANGED
@@ -249,4 +249,30 @@ class EntityTest < ActiveSupport::TestCase
|
|
249
249
|
assert_equal [domain.entity_by_name("Galleon"), domain.entity_by_name("Stronghold")],
|
250
250
|
domain.entity_by_name("Defensible").children
|
251
251
|
end
|
252
|
+
|
253
|
+
# Namespace ===================================================================
|
254
|
+
test "namespace should return nil for models outside modules" do
|
255
|
+
create_module_model "Plane"
|
256
|
+
assert_nil create_entity(Plane).namespace
|
257
|
+
end
|
258
|
+
|
259
|
+
test "namespace should return the module name for single-module models" do
|
260
|
+
create_module_model "Saw::Plane"
|
261
|
+
assert_equal "Saw", create_entity(Saw::Plane).namespace
|
262
|
+
end
|
263
|
+
|
264
|
+
test "namespace should return the module path if more than one module" do
|
265
|
+
create_module_model "Augur::Chisel::Saw::Plane"
|
266
|
+
assert_equal "Augur::Chisel::Saw", create_entity(Augur::Chisel::Saw::Plane).namespace
|
267
|
+
end
|
268
|
+
|
269
|
+
test "namespace defaults to nil" do
|
270
|
+
create_model "Foo"
|
271
|
+
assert_nil create_entity(Foo).namespace
|
272
|
+
end
|
273
|
+
|
274
|
+
test "namespace returns appropriate modules" do
|
275
|
+
entity = Domain::Entity.new(Domain.new, "Foo::Bar::Qux")
|
276
|
+
assert_equal "Foo::Bar", entity.namespace
|
277
|
+
end
|
252
278
|
end
|
data/test/unit/graphviz_test.rb
CHANGED
@@ -106,6 +106,7 @@ class GraphvizTest < ActiveSupport::TestCase
|
|
106
106
|
begin
|
107
107
|
GraphViz.class_eval do
|
108
108
|
alias_method :old_output_and_errors_from_command, :output_and_errors_from_command
|
109
|
+
undef :output_and_errors_from_command
|
109
110
|
def output_and_errors_from_command(*args); raise end
|
110
111
|
end
|
111
112
|
assert_nothing_raised do
|
@@ -113,6 +114,7 @@ class GraphvizTest < ActiveSupport::TestCase
|
|
113
114
|
end
|
114
115
|
ensure
|
115
116
|
GraphViz.class_eval do
|
117
|
+
undef :output_and_errors_from_command
|
116
118
|
alias_method :output_and_errors_from_command, :old_output_and_errors_from_command
|
117
119
|
end
|
118
120
|
end
|
@@ -137,7 +139,7 @@ class GraphvizTest < ActiveSupport::TestCase
|
|
137
139
|
|
138
140
|
test "create should not create output if there are no connected models" do
|
139
141
|
Diagram::Graphviz.create rescue nil
|
140
|
-
assert !File.
|
142
|
+
assert !File.exist?("erd.png")
|
141
143
|
end
|
142
144
|
|
143
145
|
test "create should abort and complain if there are no connected models" do
|
@@ -147,7 +149,7 @@ class GraphvizTest < ActiveSupport::TestCase
|
|
147
149
|
rescue => e
|
148
150
|
message = e.message
|
149
151
|
end
|
150
|
-
assert_match
|
152
|
+
assert_match(/No entities found/, message)
|
151
153
|
end
|
152
154
|
|
153
155
|
test "create should abort and complain if output directory does not exist" do
|
@@ -160,7 +162,7 @@ class GraphvizTest < ActiveSupport::TestCase
|
|
160
162
|
message = e.message
|
161
163
|
end
|
162
164
|
|
163
|
-
assert_match
|
165
|
+
assert_match(/Output directory 'does_not_exist' does not exist/, message)
|
164
166
|
end
|
165
167
|
|
166
168
|
test "create should not fail when reserved words are used as node names" do
|
@@ -184,6 +186,16 @@ class GraphvizTest < ActiveSupport::TestCase
|
|
184
186
|
assert_equal '"Domain model\n\n"', diagram.graph.graph[:label].to_s
|
185
187
|
end
|
186
188
|
|
189
|
+
test "generate should add default value for splines attribute" do
|
190
|
+
create_simple_domain
|
191
|
+
assert_equal '"spline"', diagram.graph.graph[:splines].to_s
|
192
|
+
end
|
193
|
+
|
194
|
+
test "generate should add set value for splines attribute" do
|
195
|
+
create_simple_domain
|
196
|
+
assert_equal '"ortho"', diagram(splines: 'ortho').graph.graph[:splines].to_s
|
197
|
+
end
|
198
|
+
|
187
199
|
test "generate should add title with application name to graph" do
|
188
200
|
begin
|
189
201
|
Object::Quux = Module.new
|
@@ -226,7 +238,7 @@ class GraphvizTest < ActiveSupport::TestCase
|
|
226
238
|
belongs_to :bar
|
227
239
|
end
|
228
240
|
create_model "Bar"
|
229
|
-
assert_equal %Q("Bar"), find_dot_node(diagram, "m_Bar")[:label].to_gv
|
241
|
+
assert_equal %Q("{Bar}"), find_dot_node(diagram, "m_Bar")[:label].to_gv
|
230
242
|
end
|
231
243
|
|
232
244
|
test "generate should add attributes to entity html labels" do
|
@@ -244,7 +256,7 @@ class GraphvizTest < ActiveSupport::TestCase
|
|
244
256
|
belongs_to :bar
|
245
257
|
end
|
246
258
|
create_model "Bar", :column => :string, :column_two => :boolean
|
247
|
-
assert_equal %Q("Bar|column (string)\\ncolumn_two (boolean)\\n"), find_dot_node(diagram, "m_Bar")[:label].to_gv
|
259
|
+
assert_equal %Q("{Bar|column (string)\\ncolumn_two (boolean)\\n}"), find_dot_node(diagram, "m_Bar")[:label].to_gv
|
248
260
|
end
|
249
261
|
|
250
262
|
test "generate should not add any attributes to entity labels if attributes is set to false" do
|
@@ -255,50 +267,58 @@ class GraphvizTest < ActiveSupport::TestCase
|
|
255
267
|
assert_no_match %r{contents}, find_dot_node(diagram(:attributes => false), "m_Jar")[:label].to_gv
|
256
268
|
end
|
257
269
|
|
258
|
-
test "node html labels should have direction reversing braces for
|
270
|
+
test "node html labels should have direction reversing braces for horizontal orientation" do
|
259
271
|
RailsERD.options.markup = true
|
260
272
|
create_model "Book", :author => :references do
|
261
273
|
belongs_to :author
|
262
274
|
end
|
263
275
|
create_model "Author", :name => :string
|
264
|
-
assert_match %r(\A<\{\s*<.*\|.*>\s*\}>\Z)m, find_dot_node(diagram(:orientation => :
|
276
|
+
assert_match %r(\A<\{\s*<.*\|.*>\s*\}>\Z)m, find_dot_node(diagram(:orientation => :horizontal), "m_Author")[:label].to_gv
|
265
277
|
end
|
266
278
|
|
267
|
-
test "node html labels should not have direction reversing braces for
|
279
|
+
test "node html labels should not have direction reversing braces for vertical orientation" do
|
268
280
|
RailsERD.options.markup = true
|
269
281
|
create_model "Book", :author => :references do
|
270
282
|
belongs_to :author
|
271
283
|
end
|
272
284
|
create_model "Author", :name => :string
|
273
|
-
assert_match %r(\A<\s*<.*\|.*>\s*>\Z)m, find_dot_node(diagram(:orientation => :
|
285
|
+
assert_match %r(\A<\s*<.*\|.*>\s*>\Z)m, find_dot_node(diagram(:orientation => :vertical), "m_Author")[:label].to_gv
|
274
286
|
end
|
275
287
|
|
276
|
-
test "node record labels should have direction reversing braces for
|
288
|
+
test "node record labels should have direction reversing braces for horizontal orientation" do
|
277
289
|
RailsERD.options.markup = false
|
278
290
|
create_model "Book", :author => :references do
|
279
291
|
belongs_to :author
|
280
292
|
end
|
281
293
|
create_model "Author", :name => :string
|
282
|
-
assert_match %r(\A"\{\w
|
294
|
+
assert_match %r(\A"\{\w+\|.*\}"\Z)m, find_dot_node(diagram(:orientation => :horizontal), "m_Author")[:label].to_gv
|
283
295
|
end
|
284
296
|
|
285
|
-
test "node record labels should not have direction reversing braces for
|
297
|
+
test "node record labels should not have direction reversing braces for vertical orientation" do
|
286
298
|
RailsERD.options.markup = false
|
287
299
|
create_model "Book", :author => :references do
|
288
300
|
belongs_to :author
|
289
301
|
end
|
290
302
|
create_model "Author", :name => :string
|
291
|
-
assert_match %r(\A"\w
|
303
|
+
assert_match %r(\A"\w+\|.*"\Z)m, find_dot_node(diagram(:orientation => :vertical), "m_Author")[:label].to_gv
|
292
304
|
end
|
293
305
|
|
294
306
|
test "generate should create edge for each relationship" do
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
307
|
+
# TODO: Once we drop Rails 3.2 support, we _should_ be able to drop the
|
308
|
+
# :respond_to? check
|
309
|
+
#
|
310
|
+
if respond_to? :skip
|
311
|
+
skip("multiple edges between the same objects can cause segfaults in some versions of Graphviz")
|
312
|
+
|
313
|
+
create_model "Foo", :bar => :references do
|
314
|
+
belongs_to :bar
|
315
|
+
end
|
316
|
+
create_model "Bar", :foo => :references do
|
317
|
+
belongs_to :foo
|
318
|
+
end
|
319
|
+
|
320
|
+
assert_equal [["m_Bar", "m_Foo"], ["m_Foo", "m_Bar"]], find_dot_node_pairs(diagram).sort
|
300
321
|
end
|
301
|
-
assert_equal [["m_Bar", "m_Foo"], ["m_Foo", "m_Bar"]], find_dot_node_pairs(diagram).sort
|
302
322
|
end
|
303
323
|
|
304
324
|
test "generate should create edge to polymorphic entity if polymorphism is true" do
|
data/test/unit/rake_task_test.rb
CHANGED
@@ -20,6 +20,7 @@ class RakeTaskTest < ActiveSupport::TestCase
|
|
20
20
|
Object::Quux = Module.new
|
21
21
|
Object::Quux::Application = Class.new
|
22
22
|
Object::Rails = Struct.new(:application).new(Object::Quux::Application.new)
|
23
|
+
|
23
24
|
Rails.class_eval do
|
24
25
|
define_method :backtrace_cleaner do
|
25
26
|
ActiveSupport::BacktraceCleaner.new.tap do |cleaner|
|
@@ -40,7 +41,7 @@ class RakeTaskTest < ActiveSupport::TestCase
|
|
40
41
|
|
41
42
|
test "generate task should not create output if there are no connected models" do
|
42
43
|
Rake::Task["erd:generate"].execute rescue nil
|
43
|
-
assert !File.
|
44
|
+
assert !File.exist?("erd.dot")
|
44
45
|
end
|
45
46
|
|
46
47
|
test "generate task should eager load application environment" do
|
@@ -100,12 +101,13 @@ class RakeTaskTest < ActiveSupport::TestCase
|
|
100
101
|
rescue => e
|
101
102
|
message = e.message
|
102
103
|
end
|
103
|
-
assert_match
|
104
|
+
assert_match(/#{Regexp.escape(<<-MSG.strip).gsub("xxx", ".*?")}/, message
|
104
105
|
Loading models failed!
|
105
106
|
Error occurred while loading application: FooBar (RuntimeError)
|
106
107
|
test/unit/rake_task_test.rb:#{l1}:in `xxx'
|
107
108
|
test/unit/rake_task_test.rb:#{l2}:in `xxx'
|
108
109
|
MSG
|
110
|
+
)
|
109
111
|
end
|
110
112
|
|
111
113
|
test "generate task should reraise if application could not be loaded and trace option is enabled" do
|
@@ -171,4 +173,22 @@ Error occurred while loading application: FooBar (RuntimeError)
|
|
171
173
|
Rake::Task["erd:options"].execute
|
172
174
|
assert_equal %w[content timestamps], RailsERD.options.attributes
|
173
175
|
end
|
176
|
+
|
177
|
+
test "options task should set known integer command line options when value is only digits" do
|
178
|
+
ENV["only_recursion_depth"] = "2"
|
179
|
+
Rake::Task["erd:options"].execute
|
180
|
+
assert_equal 2, RailsERD.options.only_recursion_depth
|
181
|
+
end
|
182
|
+
|
183
|
+
test "options task sets known command line options as symbols when not boolean or numeric" do
|
184
|
+
ENV["only_recursion_depth"] = "test"
|
185
|
+
Rake::Task["erd:options"].execute
|
186
|
+
assert_equal :test, RailsERD.options.only_recursion_depth
|
187
|
+
end
|
188
|
+
|
189
|
+
test "options task should set single parameter to only as array xxx" do
|
190
|
+
ENV["only"] = "model"
|
191
|
+
Rake::Task["erd:options"].execute
|
192
|
+
assert_equal ["model"], RailsERD.options.only
|
193
|
+
end
|
174
194
|
end
|
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-erd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rolf Timmermans
|
8
|
-
|
8
|
+
- Kerri Miller
|
9
|
+
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2021-02-16 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: activerecord
|
@@ -16,28 +17,28 @@ dependencies:
|
|
16
17
|
requirements:
|
17
18
|
- - ">="
|
18
19
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
20
|
+
version: '4.2'
|
20
21
|
type: :runtime
|
21
22
|
prerelease: false
|
22
23
|
version_requirements: !ruby/object:Gem::Requirement
|
23
24
|
requirements:
|
24
25
|
- - ">="
|
25
26
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
27
|
+
version: '4.2'
|
27
28
|
- !ruby/object:Gem::Dependency
|
28
29
|
name: activesupport
|
29
30
|
requirement: !ruby/object:Gem::Requirement
|
30
31
|
requirements:
|
31
32
|
- - ">="
|
32
33
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
34
|
+
version: '4.2'
|
34
35
|
type: :runtime
|
35
36
|
prerelease: false
|
36
37
|
version_requirements: !ruby/object:Gem::Requirement
|
37
38
|
requirements:
|
38
39
|
- - ">="
|
39
40
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
41
|
+
version: '4.2'
|
41
42
|
- !ruby/object:Gem::Dependency
|
42
43
|
name: ruby-graphviz
|
43
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,10 +67,39 @@ dependencies:
|
|
66
67
|
- - "~>"
|
67
68
|
- !ruby/object:Gem::Version
|
68
69
|
version: 0.2.0
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: pry
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: pry-nav
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
69
98
|
description: Automatically generate an entity-relationship diagram (ERD) for your
|
70
99
|
Rails models.
|
71
100
|
email:
|
72
101
|
- r.timmermans@voormedia.com
|
102
|
+
- kerrizor@kerrizor.com
|
73
103
|
executables:
|
74
104
|
- erd
|
75
105
|
extensions: []
|
@@ -117,7 +147,7 @@ homepage: https://github.com/voormedia/rails-erd
|
|
117
147
|
licenses:
|
118
148
|
- MIT
|
119
149
|
metadata: {}
|
120
|
-
post_install_message:
|
150
|
+
post_install_message:
|
121
151
|
rdoc_options: []
|
122
152
|
require_paths:
|
123
153
|
- lib
|
@@ -125,16 +155,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
125
155
|
requirements:
|
126
156
|
- - ">="
|
127
157
|
- !ruby/object:Gem::Version
|
128
|
-
version:
|
158
|
+
version: '2.2'
|
129
159
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
160
|
requirements:
|
131
161
|
- - ">="
|
132
162
|
- !ruby/object:Gem::Version
|
133
163
|
version: '0'
|
134
164
|
requirements: []
|
135
|
-
rubyforge_project:
|
136
|
-
rubygems_version: 2.
|
137
|
-
signing_key:
|
165
|
+
rubyforge_project:
|
166
|
+
rubygems_version: 2.6.14
|
167
|
+
signing_key:
|
138
168
|
specification_version: 4
|
139
169
|
summary: Entity-relationship diagram for your Rails models.
|
140
170
|
test_files:
|
@@ -152,4 +182,3 @@ test_files:
|
|
152
182
|
- test/unit/rake_task_test.rb
|
153
183
|
- test/unit/relationship_test.rb
|
154
184
|
- test/unit/specialization_test.rb
|
155
|
-
has_rdoc:
|