jw-rails-erd 1.4.5

Sign up to get free protection for your applications and to get access to all the features.
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