mongoid-erd 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +5 -0
- data/README.rdoc +60 -0
- data/bin/merd +59 -0
- data/examples/models/hello.rb +10 -0
- data/examples/models/world.rb +6 -0
- data/examples/mongoid_erd.yml +6 -0
- data/lib/mongoid_erd.rb +211 -0
- data/mongoid-erd.gemspec +18 -0
- metadata +92 -0
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
= Mongoid-ERD
|
2
|
+
|
3
|
+
Create model-diagram (ERD graph) via Graphviz using the mongoid's model source code.
|
4
|
+
|
5
|
+
Export a executable <tt>merd</tt>:
|
6
|
+
|
7
|
+
$ merd | dot -Tpng > docs/erd.png
|
8
|
+
|
9
|
+
or:
|
10
|
+
|
11
|
+
$ merd --model_dir=other/models | dot -Tpng > docs/erd.png
|
12
|
+
|
13
|
+
You can also create only a subset of models by:
|
14
|
+
|
15
|
+
$ merd --include=Class1,Class2
|
16
|
+
|
17
|
+
or
|
18
|
+
|
19
|
+
$ merd --exclude=Class4,Class5
|
20
|
+
|
21
|
+
You need to add <tt>dot</tt> executable in your PATH.
|
22
|
+
|
23
|
+
= Class, Inherits and Fields
|
24
|
+
|
25
|
+
- <tt>class xxx:yyy</tt>: a record xxx inherit from yyy. The first class that contains <tt>include Mongoid::Document</tt> on the file will be tracked. yyy will be created as a <tt>box</tt> node if no corresponding file found.
|
26
|
+
|
27
|
+
- <tt>field :xxx, type:yyy</tt>: a field xxx with type yyy. <tt>type</tt> and <tt>default</tt> will be tracked.
|
28
|
+
|
29
|
+
- <tt>embeds_many/embeds_one/has_many/has_one :xxx, :as => :yyy</tt>: field and a belong_to/embeds_many/embeds_one/has_one link to xxx
|
30
|
+
|
31
|
+
- <tt>belongs_to/embedded_in :xxx</tt>: a field without link.
|
32
|
+
|
33
|
+
Methods before <tt>:private</tt> keyword will also be tracked.
|
34
|
+
|
35
|
+
= Special Markers
|
36
|
+
|
37
|
+
<tt>erd_tag user.core</tt> tag name of the current class (see configuration files below)
|
38
|
+
|
39
|
+
<tt>erd{}</tt>
|
40
|
+
|
41
|
+
<tt>class xxx:yyy # erd{fillcolor:xxx} yyy</tt>: yyy will become the label of the current class.
|
42
|
+
|
43
|
+
<tt>field ... # erd{...} xxx</tt>: xxx will become the label of that field.
|
44
|
+
|
45
|
+
<tt>embeds_many ... # erd{...} yyy</tt>: yyy will become the label of that edge.
|
46
|
+
|
47
|
+
<tt>erd -> node_name{color:yyy} label</tt>: arbitrage edge with attributes.
|
48
|
+
|
49
|
+
= Configuration Files
|
50
|
+
|
51
|
+
<tt>config/mongoid_erd.yml</tt>:
|
52
|
+
|
53
|
+
user: {shape: Mrecord}
|
54
|
+
user.core: {fillcolor: blue}
|
55
|
+
|
56
|
+
<tt>user.core</tt> attributes will be merged to <tt>user</tt>, then should be merged into <tt>class ... # erd{attrs}</tt>. Those attributes will passed to dot language as the attributes associate to the class node.
|
57
|
+
|
58
|
+
An other will to use tag is restrict output contains only classes with specific tag:
|
59
|
+
|
60
|
+
$ merd --tag=user
|
data/bin/merd
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim :set syn=ruby
|
3
|
+
|
4
|
+
$: << 'lib'
|
5
|
+
|
6
|
+
require "mongoid_erd"
|
7
|
+
require "optparse"
|
8
|
+
require "active_support/core_ext/string/inflections"
|
9
|
+
|
10
|
+
options = {}
|
11
|
+
|
12
|
+
optparse = OptionParser.new do |opts|
|
13
|
+
opts.banner = "Usage: merd [options]"
|
14
|
+
|
15
|
+
# configuration file
|
16
|
+
options[:conf_file] = "config/mongoid_erd.yml"
|
17
|
+
opts.on('-c', '--conf_file FILE', 'use configuration file other than config/mongoid_erd.yml' ) do |file|
|
18
|
+
options[:conf_file] = file
|
19
|
+
end
|
20
|
+
|
21
|
+
# directory contains model files
|
22
|
+
options[:model_dir] = "app/models"
|
23
|
+
opts.on('-d', '--model_dir DIR', 'parse model directory other than app/models') do |dir|
|
24
|
+
options[:model_dir] = dir
|
25
|
+
end
|
26
|
+
|
27
|
+
# include/exclude model list
|
28
|
+
options[:include] = []
|
29
|
+
opts.on('-i', '--include CLASS,LIST', 'include only specified models') do |list|
|
30
|
+
options[:include] = list.split(/\s*,\s*/).map{|s| s.underscore}
|
31
|
+
end
|
32
|
+
|
33
|
+
options[:exclude] = []
|
34
|
+
opts.on('-e', '--exclude CLASS,LIST', 'exclude those specified models') do |list|
|
35
|
+
options[:exclude] = list.split(/\s*,\s*/).map{|s| s.underscore}
|
36
|
+
end
|
37
|
+
|
38
|
+
# restrict output for models with those tag only
|
39
|
+
options[:tag] = []
|
40
|
+
opts.on('-t', '--tag TAG,LIST', 'restrict output for models with those tag only') do |list|
|
41
|
+
options[:tag] = list.split(/\s*,\s*/)
|
42
|
+
end
|
43
|
+
|
44
|
+
# output file for dot source
|
45
|
+
options[:output] = ''
|
46
|
+
opts.on('-o', '--output FILE', 'save dot language source to a file other than STDOUT') do |output|
|
47
|
+
options[:output] = output
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.on_tail( '-h', '--help', 'Display this screen' ) do
|
51
|
+
puts opts
|
52
|
+
exit
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
optparse.parse!(ARGV)
|
57
|
+
config = options.select {|k,v| %w[tag conf_file model_dir exclude include output].include? k.to_s}
|
58
|
+
|
59
|
+
MongoidErd.new(config).parse.output()
|
data/lib/mongoid_erd.rb
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
require "rviz"
|
2
|
+
require "yaml"
|
3
|
+
require "active_support/core_ext/string/inflections"
|
4
|
+
|
5
|
+
class Fields
|
6
|
+
attr_accessor :name, :erd_label, :type, :edge
|
7
|
+
def as_row
|
8
|
+
str = type == "function" ? '+ ' + name : '- ' + name
|
9
|
+
str += ":" + type unless type == "function"
|
10
|
+
str += ", #{erd_label}" if erd_label and erd_label.size > 0
|
11
|
+
str
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Model
|
16
|
+
attr_accessor :name, :erd_label, :attrs, :fields, :tag, :parent
|
17
|
+
def initialize
|
18
|
+
@attrs = Hash.new
|
19
|
+
@fields = Array.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def title
|
23
|
+
"- [#{name}:#{erd_label}] -"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class MongoidErd
|
28
|
+
|
29
|
+
def initialize config = {}
|
30
|
+
@config = config
|
31
|
+
# tag: [], tags: {}, include = [], exclude = [], model_dir = ""
|
32
|
+
|
33
|
+
# parse the option file
|
34
|
+
if @config[:conf_file] and File.exists? @config[:conf_file]
|
35
|
+
yml = YAML.load(File.open(@config[:conf_file], 'r:utf-8').read)
|
36
|
+
@config[:tags] = yml["tags"]
|
37
|
+
@config[:title] = yml["title"]
|
38
|
+
else
|
39
|
+
raise "#{@config[:conf_file]} does not exists" if @config[:conf_file]
|
40
|
+
end
|
41
|
+
|
42
|
+
# merge tag attributes recursively
|
43
|
+
@config[:tags].each do |t,v|
|
44
|
+
tv = @config[:tags]["_default"].clone || {}
|
45
|
+
p = nil
|
46
|
+
t.split('.').each do |pt|
|
47
|
+
p = p ? [p, pt].join('.') : pt # merge in order: tv < a < a.b < a.b.c, ...
|
48
|
+
# puts "merge from #{p} to #{t}"
|
49
|
+
tv.merge! @config[:tags][p] if @config[:tags][p]
|
50
|
+
end
|
51
|
+
@config[:tags][t] = tv
|
52
|
+
end
|
53
|
+
|
54
|
+
@models = Hash.new
|
55
|
+
end
|
56
|
+
|
57
|
+
def set key, value
|
58
|
+
@config[key] = value
|
59
|
+
end
|
60
|
+
|
61
|
+
def get key
|
62
|
+
@config[key]
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse_erd o, line
|
66
|
+
if /erd\{(?<yml_>.+?)\}:?/ =~ line
|
67
|
+
o.attrs.merge! YAML.load(yml_) if yml_
|
68
|
+
end
|
69
|
+
if /erd(\{.+?\})?\s*:?\s+(?<label_>.+)/ =~ line
|
70
|
+
o.erd_label = label_ if label_
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# parse the fold contains mongoid source
|
75
|
+
def parse
|
76
|
+
raise "directory #{@config[:model_dir]} not exists" unless File.directory? @config[:model_dir]
|
77
|
+
Dir["#{@config[:model_dir]}/*.rb"].each do |file|
|
78
|
+
crt_model = Model.new
|
79
|
+
model_attrs_ = Hash.new
|
80
|
+
in_public = true
|
81
|
+
File.open(file, 'r:utf-8').each do |line|
|
82
|
+
line.chomp!
|
83
|
+
|
84
|
+
# erd_tag and attr
|
85
|
+
if /^[\#\s]*erd_tag\:?\s*(?<tag_>[\w\.]+)/ =~ line
|
86
|
+
crt_model.tag = tag_
|
87
|
+
crt_model.attrs = @config[:tags][tag_]
|
88
|
+
end
|
89
|
+
|
90
|
+
# catch class definition
|
91
|
+
if /^\s*class\s+(?<name_>\w+)/ =~ line
|
92
|
+
crt_model.name = name_.underscore
|
93
|
+
self.parse_erd crt_model, line
|
94
|
+
if /^\s*class\s+\w+\s+\<\s+(?<parent_>\w+)/ =~ line
|
95
|
+
crt_model.parent = parent_.underscore if parent_
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# catch functions
|
100
|
+
in_public = true if /public\:/ =~ line
|
101
|
+
in_public = false if /private\:/ =~ line
|
102
|
+
|
103
|
+
if /^\s*def\s+(?<func_>[^#]+)\s*/ =~ line
|
104
|
+
field_ = Fields.new
|
105
|
+
field_.name, field_.type = func_, 'function'
|
106
|
+
self.parse_erd field_, line # parse erd attr and label
|
107
|
+
# arbitrage link
|
108
|
+
if /\-\>\s*(?<name_>\w+)(\{(?<attrs_>.+)\})?/ =~ line
|
109
|
+
attrs = {}
|
110
|
+
attrs = YAML.load(attrs_) if attrs_
|
111
|
+
field_.edge = [name_, '', attrs]
|
112
|
+
end
|
113
|
+
crt_model.fields << field_
|
114
|
+
end
|
115
|
+
|
116
|
+
# catch field
|
117
|
+
if /^\s*field\s+\:(?<name_>\w+)\s*\,.*\:?type\:?\s*(?<type_>[A-Za-z_0-9\:]+)/ =~ line
|
118
|
+
field_ = Fields.new
|
119
|
+
field_.name, field_.type = name_, type_
|
120
|
+
self.parse_erd field_, line # parse erd attr and label
|
121
|
+
# arbitrage link
|
122
|
+
if /\-\>\s*(?<name_>\w+)(\{(?<attrs_>.+)\})?/ =~ line
|
123
|
+
attrs = {}
|
124
|
+
attrs = YAML.load(attrs_) if attrs_
|
125
|
+
field_.edge = [name_, '', attrs]
|
126
|
+
end
|
127
|
+
crt_model.fields << field_
|
128
|
+
end
|
129
|
+
|
130
|
+
# catch relations
|
131
|
+
if /^\s*(?<rel_>embeds_many|embeds_one|has_many|has_one|belongs_to|embedded_in)\s+\:(?<name_>\w+)\s*(\,.*\:?as\:?\s*(?<as_>\w+))?/ =~ line
|
132
|
+
field_ = Fields.new
|
133
|
+
field_.name, field_.type = rel_, name_
|
134
|
+
field_.name = "#{rel_} (as #{as_})" if as_
|
135
|
+
self.parse_erd field_, line # parse erd attr and label
|
136
|
+
crt_model.fields << field_
|
137
|
+
unless %w[belongs_to embedded_in].include? rel_
|
138
|
+
field_.edge = [name_, '', {label: rel_}]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# common extension field
|
143
|
+
if /^\s*symbolize\s+\:(?<name>\w+)\s*\,.*\:?in\:?.*(?<in_>\[.+\])/ =~ line
|
144
|
+
field_ = Fields.new
|
145
|
+
field_.name, field_.type = name_, "symbolized in #{in_}"
|
146
|
+
self.parse_erd field_, line # parse erd attr and label
|
147
|
+
crt_model.fields << field_
|
148
|
+
end
|
149
|
+
|
150
|
+
if /^\s*state_machine\s+\:(?<state_>\w+)/ =~ line
|
151
|
+
field_ = Fields.new
|
152
|
+
field_.name = state_ == "initial" ? "state" : state_
|
153
|
+
field_.type = "state_machine"
|
154
|
+
self.parse_erd field_, line # parse erd attr and label
|
155
|
+
crt_model.fields << field_
|
156
|
+
end
|
157
|
+
end # open and parse one file
|
158
|
+
|
159
|
+
# assign attributes at the last moment
|
160
|
+
crt_model.attrs.merge! model_attrs_
|
161
|
+
|
162
|
+
# if config.include/tag, default to exclude_it = true
|
163
|
+
if @config[:include].size > 0 or @config[:tag].size > 0
|
164
|
+
include_it = false
|
165
|
+
else
|
166
|
+
include_it = true
|
167
|
+
end
|
168
|
+
|
169
|
+
# if in the include list, include it
|
170
|
+
include_it = true if @config[:include] and @config[:include].include? crt_model.name
|
171
|
+
@config[:tag].each do |t|
|
172
|
+
include_it = true if t == crt_model.tag or /^#{t}(\..+)?/.match(crt_model.tag)
|
173
|
+
end
|
174
|
+
|
175
|
+
include_it = false if @config[:exclude].include? crt_model.name
|
176
|
+
@models[crt_model.name] = crt_model if include_it
|
177
|
+
end # open directory
|
178
|
+
self
|
179
|
+
end
|
180
|
+
|
181
|
+
def output
|
182
|
+
g = Rviz::Graph.new @config[:title], {rankdir: 'LR', dpi: 300}
|
183
|
+
|
184
|
+
@models.each do |mname, model|
|
185
|
+
g.add_record(model.name, model.attrs)
|
186
|
+
g.node(model.name).add_row(model.title, true)
|
187
|
+
model.fields.each do |field|
|
188
|
+
g.node(model.name).add_row(field.as_row, true, 'l')
|
189
|
+
if field.edge
|
190
|
+
to_node, to_anchor, attrs = field.edge[0].underscore, field.edge[1], field.edge[2]
|
191
|
+
unless @models[to_node]
|
192
|
+
g.add(to_node, 'oval', {style:'filled', fillcolor:'grey', color:'grey'})
|
193
|
+
to_anchor = ''
|
194
|
+
end
|
195
|
+
g.link(model.name, field.as_row, to_node, to_anchor, attrs)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# relations
|
200
|
+
if model.parent
|
201
|
+
unless @models[model.parent]
|
202
|
+
g.add(model.parent, 'oval', {style:'filled', fillcolor:'grey', color:'grey'})
|
203
|
+
end
|
204
|
+
g.link(model.name, model.title, model.parent, '', {arrowhead: 'onormal'})
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
g.output
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
data/mongoid-erd.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new 'mongoid-erd', '0.0.3' do |s|
|
4
|
+
s.executables << "merd"
|
5
|
+
s.description = "Create model diagram graph (ERD graph) in graphviz's dot language"
|
6
|
+
s.summary = "Mongoid ERD diagram creator"
|
7
|
+
s.authors = ["Huang Wei"]
|
8
|
+
s.email = "huangw@pe-po.com"
|
9
|
+
s.homepage = "https://github.com/huangw/mongoid-erd-gem"
|
10
|
+
s.files = `git ls-files`.split("\n") - %w[.gitignore]
|
11
|
+
s.test_files = Dir.glob("{spec,test}/**/*.rb")
|
12
|
+
s.rdoc_options = %w[--line-numbers --inline-source --title Rviz --main README.rdoc --encoding=UTF-8]
|
13
|
+
|
14
|
+
s.add_dependency 'rviz'
|
15
|
+
s.add_dependency 'active_support'
|
16
|
+
# s.add_development_dependency 'rspec', '~> 2.5'
|
17
|
+
end
|
18
|
+
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mongoid-erd
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Huang Wei
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-10 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rviz
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: active_support
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: Create model diagram graph (ERD graph) in graphviz's dot language
|
47
|
+
email: huangw@pe-po.com
|
48
|
+
executables:
|
49
|
+
- merd
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- Gemfile
|
54
|
+
- README.rdoc
|
55
|
+
- bin/merd
|
56
|
+
- examples/models/hello.rb
|
57
|
+
- examples/models/world.rb
|
58
|
+
- examples/mongoid_erd.yml
|
59
|
+
- lib/mongoid_erd.rb
|
60
|
+
- mongoid-erd.gemspec
|
61
|
+
homepage: https://github.com/huangw/mongoid-erd-gem
|
62
|
+
licenses: []
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options:
|
65
|
+
- --line-numbers
|
66
|
+
- --inline-source
|
67
|
+
- --title
|
68
|
+
- Rviz
|
69
|
+
- --main
|
70
|
+
- README.rdoc
|
71
|
+
- --encoding=UTF-8
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ! '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
requirements: []
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 1.8.24
|
89
|
+
signing_key:
|
90
|
+
specification_version: 3
|
91
|
+
summary: Mongoid ERD diagram creator
|
92
|
+
test_files: []
|