mongoid-erd 0.0.3
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.
- 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: []
|