25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
|
# File 'lib/modaldiagrams/modaldiagrams.rb', line 25
def generate(cfg)
classes = {""=>[]}
relations = []
n_m = []
rep = []
cluster_classes = {}
relation_classes = []
model_selection_options = {
:all_models => cfg.include_all_models,
:dynamic_models => cfg.include_dynamic_models,
:include_files => false
}
models = dbmodels(model_selection_options.merge(:exclude_sti_models => true))
models.each do |cls|
if cls.respond_to?(:reflect_on_all_associations) && ActiveRecord::Base.connection.table_exists?(cls.table_name)
columns = cls.columns.reject { |c| c.primary || c.name =~ /(_count)$/ || c.name == cls.inheritance_column || c.name =~ /^(created_at|updated_at)$/ }.map{|c| field_spec(cfg, c)}
columns_to_ignore = cls.reflect_on_all_associations.map{|a|
cols = []
if a.macro == :belongs_to
cols << assoc_foreign_key(a)
cols << assoc_foreign_type(a) if a.options[:polymorphic]
end
cols
}.flatten.compact.uniq
columns.reject!{|c| c.split(':').first.strip.in? columns_to_ignore}
if cfg.sti_fields && cls.respond_to?(:fields_info) && cls.fields_info!=:omitted
columns.reject!{|c| !c.split(':').first.strip.in? cls.fields_info.map{|c| c.name.to_s}}
end
columns = columns.to(cfg.max_attributes) + ['...'] if columns.size > cfg.max_attributes
if cls.respond_to?(:cluster)
cluster = cls.cluster.to_s
classes[cluster] ||= []
else
cluster = ""
end
classes[cluster] << %{"#{cls}" [shape=Mrecord, label="{#{cls}|#{columns.join('\\l')}\\l}"]}
cluster_classes[cluster] ||= []
cluster_classes[cluster] << cls.to_s
cls.reflect_on_all_associations.each do |assoc|
target,type = nil,nil
case assoc.macro
when :has_many
unless assoc.options[:through]
target = assoc.class_name
type = :one_to_many
if !cfg.show_multiple && rep.include?([cls.to_s, target.to_s])
target = type = nil
else
rep << [cls.to_s,target.to_s]
end
end
when :has_one
target = assoc.class_name
type = :one_to_one
when :has_and_belongs_to_many
target = assoc.class_name
type = :many_to_many
if n_m.include?([target.to_s, cls.to_s])
target = type = nil
else
n_m << [cls.to_s,target.to_s]
end
end
if target
pk = assoc_foreign_key(assoc)
if pk != cls.to_s.underscore+"_id"
label = pk.to_s
label = label[0..-4] if label[-3..-1]=='_id'
if label.size<=15
label = "headlabel=#{label}, "
else
label = nil
end
else
label = nil
end
poly = false
if iassoc = target.constantize.reflect_on_all_associations.detect{|a| !a.options[:through] && pk==assoc_foreign_key(a)}
if iassoc.options[:polymorphic]
poly = true
end
end
case type
when :one_to_one
tail = "none"
head = poly ? cfg.arrow_heads.poly_single : cfg.arrow_heads.single
when :one_to_many
tail = "none"
head = poly ? cfg.arrow_heads.poly_multiple : cfg.arrow_heads.multiple
when :many_to_many
tail = cfg.arrow_heads.multiple
head = cfg.arrow_heads.multiple
end
samehead = (cfg.unified_polymorphic && label) ? %{, samehead="#{label}"} : ''
label = nil if cfg.no_association_labels
relations << %{"#{cls}" -> "#{target}" [arrowtail=#{tail}, arrowhead=#{head}#{samehead} #{label}dir=both]}
relation_classes << [cls.to_s, target.to_s]
end
end
end
end
if cfg.show_sti
sti_classes = dbmodels(model_selection_options.merge(:exclude_non_sti_models => true))
sti_classes.each do |sti_class|
cls = sti_class.base_class
if cls.respond_to?(:cluster)
cluster = cls.cluster.to_s
else
cluster = ""
end
if cfg.sti_fields && sti_class.respond_to?(:fields_info) && sti_class.fields_info!=:omitted
columns = sti_class.fields_info.map{|c| field_spec(cfg, c)}
columns = columns.to(cfg.max_attributes) + ['...'] if columns.size > cfg.max_attributes
else
columns = nil
end
classes[cluster] ||= []
classes[cluster] << %{"#{sti_class}" [shape=Mrecord, label="{#{sti_class}\\l}"]}
cluster_classes[cluster] ||= []
cluster_classes[cluster] << sti_class.to_s
if columns
classes[cluster] << %{"#{sti_class}" [shape=Mrecord, label="{#{sti_class}|#{columns.join('\\l')}\\l}"]}
end
base_class = sti_class.superclass
relations << %{"#{base_class}" -> "#{sti_class}" [arrowtail=onormal, arrowhead=none, dir=both]}
relation_classes << [base_class.to_s, sti_class.to_s]
end
end
fn = Rails.root.join('db/diagrams/diagram.dot')
mkdir_p fn.dirname
File.open(fn,'w') do |f|
f
cluster_id = 0
all_classes = []
classes.keys.each do |cluster|
next if cluster.in? cfg.clusters_not_shown_on_main_diagram
next if cluster_classes[cluster].nil?
all_classes += cluster_classes[cluster]
cluster_id += 1
add_diagram_classes f, classes[cluster], cluster, cluster_id, cfg.show_cluster_boxes
end
add_diagram_relations f, relations, relation_classes, all_classes, cfg.show_external
f
end
classes.keys.each do |cluster|
fn = "db/diagrams/diagram_#{cluster.downcase}.dot"
File.open(fn,'w') do |f|
f
add_diagram_classes f, classes[cluster]
add_diagram_relations f, relations, relation_classes, cluster_classes[cluster], cfg.show_external
f
end
end
puts "The diagrams have been written to #{fn}"
end
|