jw-rails-erd 1.4.5

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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +86 -0
  3. data/Rakefile +20 -0
  4. data/bin/erd +4 -0
  5. data/lib/generators/erd/USAGE +4 -0
  6. data/lib/generators/erd/install_generator.rb +14 -0
  7. data/lib/generators/erd/templates/auto_generate_diagram.rake +6 -0
  8. data/lib/rails-erd.rb +1 -0
  9. data/lib/rails_erd/cli.rb +164 -0
  10. data/lib/rails_erd/config.rb +97 -0
  11. data/lib/rails_erd/custom.rb +99 -0
  12. data/lib/rails_erd/diagram/graphviz.rb +295 -0
  13. data/lib/rails_erd/diagram/templates/node.html.erb +14 -0
  14. data/lib/rails_erd/diagram/templates/node.record.erb +4 -0
  15. data/lib/rails_erd/diagram.rb +188 -0
  16. data/lib/rails_erd/domain/attribute.rb +160 -0
  17. data/lib/rails_erd/domain/entity.rb +104 -0
  18. data/lib/rails_erd/domain/relationship/cardinality.rb +118 -0
  19. data/lib/rails_erd/domain/relationship.rb +203 -0
  20. data/lib/rails_erd/domain/specialization.rb +90 -0
  21. data/lib/rails_erd/domain.rb +153 -0
  22. data/lib/rails_erd/railtie.rb +10 -0
  23. data/lib/rails_erd/tasks.rake +58 -0
  24. data/lib/rails_erd/version.rb +4 -0
  25. data/lib/rails_erd.rb +73 -0
  26. data/lib/tasks/auto_generate_diagram.rake +21 -0
  27. data/test/support_files/erdconfig.another_example +3 -0
  28. data/test/support_files/erdconfig.example +19 -0
  29. data/test/support_files/erdconfig.exclude.example +19 -0
  30. data/test/test_helper.rb +160 -0
  31. data/test/unit/attribute_test.rb +316 -0
  32. data/test/unit/cardinality_test.rb +123 -0
  33. data/test/unit/config_test.rb +110 -0
  34. data/test/unit/diagram_test.rb +352 -0
  35. data/test/unit/domain_test.rb +258 -0
  36. data/test/unit/entity_test.rb +252 -0
  37. data/test/unit/graphviz_test.rb +461 -0
  38. data/test/unit/rake_task_test.rb +174 -0
  39. data/test/unit/relationship_test.rb +476 -0
  40. data/test/unit/specialization_test.rb +67 -0
  41. metadata +155 -0
@@ -0,0 +1,258 @@
1
+ require File.expand_path("../test_helper", File.dirname(__FILE__))
2
+
3
+ class DomainTest < ActiveSupport::TestCase
4
+ # Domain ===================================================================
5
+ test "generate should return domain" do
6
+ assert_kind_of Domain, Domain.generate
7
+ end
8
+
9
+ test "name should return rails application name" do
10
+ begin
11
+ Object::Quux = Module.new
12
+ Object::Quux::Application = Class.new
13
+ Object::Rails = Struct.new(:application).new(Object::Quux::Application.new)
14
+ assert_equal "Quux", Domain.generate.name
15
+ ensure
16
+ Object::Quux.send :remove_const, :Application
17
+ Object.send :remove_const, :Quux
18
+ Object.send :remove_const, :Rails
19
+ end
20
+ end
21
+
22
+ test "name should return nil outside rails" do
23
+ assert_nil Domain.generate.name
24
+ end
25
+
26
+ test "inspect should display object id only" do
27
+ create_model "Foo", :bar => :references do
28
+ belongs_to :bar
29
+ end
30
+ create_model "Bar"
31
+ assert_match %r{#<RailsERD::Domain:.*>}, Domain.generate.inspect
32
+ end
33
+
34
+ # Entity processing ========================================================
35
+ test "entity_by_name should return associated entity for given name" do
36
+ create_model "Foo"
37
+ assert_equal Foo, Domain.generate.entity_by_name("Foo").model
38
+ end
39
+
40
+ test "entities should return domain entities" do
41
+ create_models "Foo", "Bar"
42
+ assert_equal [Domain::Entity] * 2, Domain.generate.entities.collect(&:class)
43
+ end
44
+
45
+ test "entities should return all domain entities sorted by name" do
46
+ create_models "Foo", "Bar", "Baz", "Qux"
47
+ assert_equal [Bar, Baz, Foo, Qux], Domain.generate.entities.collect(&:model)
48
+ end
49
+
50
+ test "entities should include abstract entities" do
51
+ create_model "Stronghold" do
52
+ has_many :cannons, :as => :defensible
53
+ end
54
+ assert_equal ["Defensible", "Stronghold"], Domain.generate.entities.collect(&:name)
55
+ end
56
+
57
+ test "entities should include abstract entities only once" do
58
+ create_model "Stronghold" do
59
+ has_many :cannons, :as => :defensible
60
+ end
61
+ create_model "Galleon" do
62
+ has_many :cannons, :as => :defensible
63
+ end
64
+ assert_equal ["Defensible", "Galleon", "Stronghold"], Domain.generate.entities.collect(&:name)
65
+ end
66
+
67
+ test "entities should include abstract models" do
68
+ create_model "Structure" do
69
+ self.abstract_class = true
70
+ end
71
+ create_model "Palace", Structure
72
+ assert_equal ["Palace", "Structure"], Domain.generate.entities.collect(&:name)
73
+ end
74
+
75
+ # Relationship processing ==================================================
76
+ test "relationships should return empty array for empty domain" do
77
+ assert_equal [], Domain.generate.relationships
78
+ end
79
+
80
+ test "relationships should return relationships in domain model" do
81
+ create_models "Baz", "Qux"
82
+ create_model "Foo", :bar => :references, :qux => :references do
83
+ belongs_to :bar
84
+ belongs_to :qux
85
+ end
86
+ create_model "Bar", :baz => :references do
87
+ belongs_to :baz
88
+ end
89
+ assert_equal [Domain::Relationship] * 3, Domain.generate.relationships.collect(&:class)
90
+ end
91
+
92
+ test "relationships should count mutual relationship as one" do
93
+ create_model "Foo", :bar => :references do
94
+ belongs_to :bar
95
+ end
96
+ create_model "Bar" do
97
+ has_many :foos
98
+ end
99
+ assert_equal [Domain::Relationship], Domain.generate.relationships.collect(&:class)
100
+ end
101
+
102
+ test "relationships should count mutual indirect relationship as one" do
103
+ create_model "Wizard" do
104
+ has_many :spell_masteries
105
+ has_many :spells, :through => :spell_masteries
106
+ end
107
+ create_model "Spell" do
108
+ has_many :spell_masteries
109
+ has_many :wizards, :through => :spell_masteries
110
+ end
111
+ create_model "SpellMastery", :wizard => :references, :spell => :references do
112
+ belongs_to :wizard
113
+ belongs_to :spell
114
+ end
115
+ assert_equal [Domain::Relationship], Domain.generate.relationships.select(&:indirect?).collect(&:class)
116
+ end
117
+
118
+ test "relationships should count relationship between same models with distinct foreign key seperately" do
119
+ create_model "Foo", :bar => :references, :special_bar => :references do
120
+ belongs_to :bar
121
+ end
122
+ create_model "Bar" do
123
+ has_many :foos, :foreign_key => :special_bar_id
124
+ end
125
+ assert_equal [Domain::Relationship] * 2, Domain.generate.relationships.collect(&:class)
126
+ end
127
+
128
+ test "relationships should use model name first in alphabet as source for many to many relationships" do
129
+ create_table "many_more", :many_id => :integer, :more_id => :integer
130
+ create_model "Many" do
131
+ has_and_belongs_to_many :more
132
+ end
133
+ create_model "More" do
134
+ has_and_belongs_to_many :many
135
+ end
136
+ relationship = Domain.generate.relationships.first
137
+ assert_equal ["Many", "More"], [relationship.source.name, relationship.destination.name]
138
+ end
139
+
140
+ # Specialization processing ================================================
141
+ test "specializations should return empty array for empty domain" do
142
+ assert_equal [], Domain.generate.specializations
143
+ end
144
+
145
+ test "specializations should return empty array for domain without single table inheritance" do
146
+ create_simple_domain
147
+ assert_equal [], Domain.generate.specializations
148
+ end
149
+
150
+ test "specializations should return specializations in domain model" do
151
+ create_specialization
152
+ assert_equal [Domain::Specialization], Domain.generate.specializations.collect(&:class)
153
+ end
154
+
155
+ test "specializations should return specializations of specializations in domain model" do
156
+ create_specialization
157
+ Object.const_set :BelgianBeer, Class.new(Beer)
158
+ assert_equal [Domain::Specialization] * 2, Domain.generate.specializations.collect(&:class)
159
+ end
160
+
161
+ test "specializations should return polymorphic generalizations in domain model" do
162
+ create_polymorphic_generalization
163
+ assert_equal [Domain::Specialization], Domain.generate.specializations.collect(&:class)
164
+ end
165
+
166
+ test "specializations should return abstract generalizations in domain model" do
167
+ create_abstract_generalization
168
+ assert_equal [Domain::Specialization], Domain.generate.specializations.collect(&:class)
169
+ end
170
+
171
+ test "specializations should return polymorphic and abstract generalizations and specializations in domain model" do
172
+ create_specialization
173
+ create_polymorphic_generalization
174
+ create_abstract_generalization
175
+ assert_equal [Domain::Specialization] * 3, Domain.generate.specializations.collect(&:class)
176
+ end
177
+
178
+ # Erroneous associations ===================================================
179
+ test "relationships should omit bad has_many associations" do
180
+ create_model "Foo" do
181
+ has_many :flabs
182
+ end
183
+ assert_equal [], Domain.generate(:warn => false).relationships
184
+ end
185
+
186
+ test "relationships should omit bad has_many through association" do
187
+ create_model "Foo" do
188
+ has_many :flabs, :through => :bars
189
+ end
190
+ assert_equal [], Domain.generate(:warn => false).relationships
191
+ end
192
+
193
+ test "relationships should omit association to model outside domain" do
194
+ create_model "Foo" do
195
+ has_many :bars
196
+ end
197
+ create_model "Bar", :foo => :references
198
+ assert_equal [], Domain.new([Foo], :warn => false).relationships
199
+ end
200
+
201
+ test "relationships should output a warning when a bad association is encountered" do
202
+ create_model "Foo" do
203
+ has_many :flabs
204
+ end
205
+ output = collect_stdout do
206
+ Domain.generate.relationships
207
+ end
208
+ assert_match /Ignoring invalid association :flabs on Foo/, output
209
+ end
210
+
211
+ test "relationships should output a warning when an association to model outside domain is encountered" do
212
+ create_model "Foo" do
213
+ has_many :bars
214
+ end
215
+ create_model "Bar", :foo => :references
216
+ output = collect_stdout do
217
+ Domain.new([Foo]).relationships
218
+ end
219
+ assert_match /model Bar exists, but is not included in domain/, output
220
+ end
221
+
222
+ test "relationships should output a warning when an association to a non existent generalization is encountered" do
223
+ create_model "Foo" do
224
+ has_many :bars, :as => :foo
225
+ end
226
+ create_model "Bar", :foobar => :references do
227
+ belongs_to :foo_bar, :polymorphic => true
228
+ end
229
+ output = collect_stdout do
230
+ Domain.generate.relationships
231
+ end
232
+ assert_match /polymorphic interface FooBar does not exist/, output
233
+ end
234
+
235
+ test "relationships should not warn when a bad association is encountered if warnings are disabled" do
236
+ create_model "Foo" do
237
+ has_many :flabs
238
+ end
239
+ output = collect_stdout do
240
+ Domain.generate(:warn => false).relationships
241
+ end
242
+ assert_equal "", output
243
+ end
244
+
245
+ # Erroneous models =========================================================
246
+ test "entities should omit bad models" do
247
+ Object.const_set :Foo, Class.new(ActiveRecord::Base)
248
+ assert_equal [], Domain.generate(:warn => false).entities
249
+ end
250
+
251
+ test "entities should output a warning when a model table does not exist" do
252
+ Object.const_set :Foo, Class.new(ActiveRecord::Base)
253
+ output = collect_stdout do
254
+ Domain.generate.entities
255
+ end
256
+ assert_match /Ignoring invalid model Foo \(table foos does not exist\)/, output
257
+ end
258
+ end
@@ -0,0 +1,252 @@
1
+ require File.expand_path("../test_helper", File.dirname(__FILE__))
2
+
3
+ class EntityTest < ActiveSupport::TestCase
4
+ def create_entity(model)
5
+ Domain::Entity.new(Domain.new, model.name, model)
6
+ end
7
+
8
+ def create_generalized_entity(name)
9
+ Domain::Entity.new(Domain.new, name)
10
+ end
11
+
12
+ def create_abstract_entity(name)
13
+ model = create_model(name) { self.abstract_class = true }
14
+ create_entity(model)
15
+ end
16
+
17
+ # Entity ===================================================================
18
+ test "model should return active record model" do
19
+ create_models "Foo"
20
+ assert_equal Foo, create_entity(Foo).model
21
+ end
22
+
23
+ test "name should return model name" do
24
+ create_models "Foo"
25
+ assert_equal "Foo", create_entity(Foo).name
26
+ end
27
+
28
+ test "spaceship should sort entities by name" do
29
+ create_models "Foo", "Bar"
30
+ foo, bar = create_entity(Foo), create_entity(Bar)
31
+ assert_equal [bar, foo], [foo, bar].sort
32
+ end
33
+
34
+ test "to_s should equal name" do
35
+ create_models "Foo"
36
+ assert_equal "Foo", create_entity(Foo).to_s
37
+ end
38
+
39
+ test "inspect should show name" do
40
+ create_models "Foo"
41
+ assert_match %r{#<RailsERD::Domain::Entity:.* @model=Foo>}, create_entity(Foo).inspect
42
+ end
43
+
44
+ test "relationships should return relationships for this model" do
45
+ create_model "Foo", :bar => :references do
46
+ belongs_to :bar
47
+ end
48
+ create_model "Bar", :baz => :references do
49
+ belongs_to :baz
50
+ end
51
+ create_model "Baz"
52
+
53
+ domain = Domain.generate
54
+ foo = domain.entity_by_name("Foo")
55
+ assert_equal domain.relationships.select { |r| r.destination == foo }, foo.relationships
56
+ end
57
+
58
+ test "relationships should return relationships that connect to this model" do
59
+ create_model "Foo", :bar => :references
60
+ create_model "Bar", :baz => :references do
61
+ belongs_to :baz
62
+ has_many :foos
63
+ end
64
+ create_model "Baz"
65
+
66
+ domain = Domain.generate
67
+ foo = domain.entity_by_name("Foo")
68
+ assert_equal domain.relationships.select { |r| r.destination == foo }, foo.relationships
69
+ end
70
+
71
+ # Entity properties ========================================================
72
+ test "connected should return false for unconnected entities" do
73
+ create_models "Foo", "Bar"
74
+ assert_equal [false, false], Domain.generate.entities.map(&:connected?)
75
+ end
76
+
77
+ test "connected should return true for connected entities" do
78
+ create_model "Foo", :bar => :references do
79
+ belongs_to :bar
80
+ end
81
+ create_model "Bar"
82
+ assert_equal [true, true], Domain.generate.entities.map(&:connected?)
83
+ end
84
+
85
+ test "disconnected should return true for unconnected entities" do
86
+ create_models "Foo", "Bar"
87
+ assert_equal [true, true], Domain.generate.entities.map(&:disconnected?)
88
+ end
89
+
90
+ test "disconnected should return false for connected entities" do
91
+ create_model "Foo", :bar => :references do
92
+ belongs_to :bar
93
+ end
94
+ create_model "Bar"
95
+ assert_equal [false, false], Domain.generate.entities.map(&:disconnected?)
96
+ end
97
+
98
+ test "specialized should return false for regular entities" do
99
+ create_model "Foo"
100
+ assert_equal false, create_entity(Foo).specialized?
101
+ end
102
+
103
+ test "specialized should return false for child entities with distinct tables" do
104
+ create_model "Foo", :type => :string
105
+ Object.const_set :SpecialFoo, Class.new(Foo)
106
+ SpecialFoo.class_eval do
107
+ self.table_name = "special_foo"
108
+ end
109
+ create_table "special_foo", {}, true
110
+ assert_equal false, create_entity(SpecialFoo).specialized?
111
+ end
112
+
113
+ test "specialized should return true for specialized entities" do
114
+ create_model "Foo", :type => :string
115
+ Object.const_set :SpecialFoo, Class.new(Foo)
116
+ assert_equal true, create_entity(SpecialFoo).specialized?
117
+ end
118
+
119
+ test "specialized should return true for specialations of specialized entities" do
120
+ create_model "Foo", :type => :string
121
+ Object.const_set :SpecialFoo, Class.new(Foo)
122
+ Object.const_set :VerySpecialFoo, Class.new(SpecialFoo)
123
+ assert_equal true, create_entity(VerySpecialFoo).specialized?
124
+ end
125
+
126
+ test "virtual should return true for specialized entity" do
127
+ create_model "Foo", :type => :string
128
+ Object.const_set :SpecialFoo, Class.new(Foo)
129
+ assert_equal true, create_entity(SpecialFoo).virtual?
130
+ end
131
+
132
+ test "generalized should return false for regular entity" do
133
+ create_model "Concrete"
134
+ assert_equal false, create_entity(Concrete).generalized?
135
+ end
136
+
137
+ test "virtual should return false for regular entity" do
138
+ create_model "Concrete"
139
+ assert_equal false, create_entity(Concrete).virtual?
140
+ end
141
+
142
+ # Attribute processing =====================================================
143
+ test "attributes should return list of attributes" do
144
+ create_model "Bar", :some_column => :integer, :another_column => :string
145
+ assert_equal [Domain::Attribute] * 3, create_entity(Bar).attributes.collect(&:class)
146
+ end
147
+
148
+ test "attributes should return attributes sorted by name" do
149
+ create_model "Bar", :some_column => :integer, :another_column => :string
150
+ assert_equal ["another_column", "id", "some_column"], create_entity(Bar).attributes.collect(&:name)
151
+ end
152
+
153
+ # Generalized entity =======================================================
154
+ test "model should return nil for generalized entity" do
155
+ assert_nil create_generalized_entity("MyAbstractModel").model
156
+ end
157
+
158
+ test "name should return given name for generalized entity" do
159
+ assert_equal "MyAbstractModel", create_generalized_entity("MyAbstractModel").name
160
+ end
161
+
162
+ test "attributes should return empty array for generalized entity" do
163
+ assert_equal [], create_generalized_entity("MyAbstractModel").attributes
164
+ end
165
+
166
+ test "generalized should return true for generalized entity" do
167
+ assert_equal true, create_generalized_entity("MyAbstractModel").generalized?
168
+ end
169
+
170
+ test "specialized should return false for generalized entity" do
171
+ assert_equal false, create_generalized_entity("MyAbstractModel").specialized?
172
+ end
173
+
174
+ test "virtual should return true for generalized entity" do
175
+ assert_equal true, create_generalized_entity("MyAbstractModel").virtual?
176
+ end
177
+
178
+ test "relationships should return relationships for generalized entity" do
179
+ create_model "Stronghold" do
180
+ has_many :cannons, :as => :defensible
181
+ end
182
+ create_model "Cannon", :defensible => :references do
183
+ belongs_to :defensible, :polymorphic => true
184
+ end
185
+
186
+ domain = Domain.generate
187
+ defensible = domain.entity_by_name("Defensible")
188
+ assert_equal domain.relationships, defensible.relationships
189
+ end
190
+
191
+ # Abstract generalized entity ==============================================
192
+ test "name should return given name for abstract generalized entity" do
193
+ assert_equal "MyAbstractModel", create_abstract_entity("MyAbstractModel").name
194
+ end
195
+
196
+ test "attributes should return empty array for abstract generalized entity" do
197
+ assert_equal [], create_abstract_entity("MyAbstractModel").attributes
198
+ end
199
+
200
+ test "generalized should return true for abstract generalized entity" do
201
+ assert_equal true, create_abstract_entity("MyAbstractModel").generalized?
202
+ end
203
+
204
+ test "specialized should return false for abstract generalized entity" do
205
+ assert_equal false, create_abstract_entity("MyAbstractModel").specialized?
206
+ end
207
+
208
+ test "virtual should return true for abstract generalized entity" do
209
+ assert_equal true, create_abstract_entity("MyAbstractModel").virtual?
210
+ end
211
+
212
+ test "relationships should return relationships for abstract generalized entity" do
213
+ create_model "Kingdom"
214
+ create_model "Structure" do
215
+ self.abstract_class = true
216
+ belongs_to :kingdom
217
+ end
218
+ create_model "Palace", Structure, :kingdom => :references
219
+
220
+ domain = Domain.generate
221
+ assert_equal domain.relationships, domain.entity_by_name("Structure").relationships
222
+ end
223
+
224
+ # Children =================================================================
225
+ test "children should return empty array for regular entities" do
226
+ create_model "Foo"
227
+ assert_equal [], create_entity(Foo).children
228
+ end
229
+
230
+ test "children should return inherited entities for regular entities with single table inheritance" do
231
+ create_model "Beverage", :type => :string
232
+ create_model "Whisky", Beverage
233
+ create_model "Beer", Beverage
234
+ domain = Domain.generate
235
+ assert_equal [domain.entity_by_name("Beer"), domain.entity_by_name("Whisky")], domain.entity_by_name("Beverage").children
236
+ end
237
+
238
+ test "children should return inherited entities for generalized entities" do
239
+ create_model "Stronghold" do
240
+ has_many :cannons, :as => :defensible
241
+ end
242
+ create_model "Galleon" do
243
+ has_many :cannons, :as => :defensible
244
+ end
245
+ create_model "Cannon", :defensible => :references do
246
+ belongs_to :defensible, :polymorphic => true
247
+ end
248
+ domain = Domain.generate
249
+ assert_equal [domain.entity_by_name("Galleon"), domain.entity_by_name("Stronghold")],
250
+ domain.entity_by_name("Defensible").children
251
+ end
252
+ end