dbmodel 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/MIT-LICENSE +21 -0
  2. data/README +72 -0
  3. data/README.txt +69 -0
  4. data/Rakefile +26 -0
  5. data/bin/dbmodel +7 -0
  6. data/lib/dbmodel.rb +295 -0
  7. metadata +55 -0
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2005 William T Katz
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
data/README ADDED
@@ -0,0 +1,72 @@
1
+ = dbmodel - rails model generator from XML database model file
2
+
3
+ === Synopsis
4
+
5
+ dbmodel [options] [xml_file_name(s) ...]
6
+
7
+ === Description
8
+
9
+ The dbmodel generator loads an XML file from a database design tool
10
+ (MySQL DBDesigner 4), generates files you'd normally get by
11
+ "generate model" or "generate scaffold", and inserts comments and
12
+ relationships (e.g., "has_many :foos") into the model files.
13
+
14
+ If no XML files are specified on the command line, dbmodel will
15
+ check if the file "dbmodel.xml" is present in the current directory.
16
+
17
+ While full roundtripping is possible in the future, this version is a
18
+ simplistic proof-of-concept. It assumes you are using the free
19
+ DBDesigner 4. Tables are created using Rails naming conventions and
20
+ relationships are named in Rails fashion. Scaffolding commands can
21
+ be embedded as comments in the tables using the format:
22
+ [SCAFFOLD]
23
+ or optionally
24
+ [SCAFFOLD controller-name action1 action2 ...]
25
+
26
+ dbmodel is simplistic and requires convention to bypass configuration.
27
+ Relationships should be labelled habtm, has_many, or has_one. Reciprocal
28
+ relationships for has_many and has_one are auto-generated. The database
29
+ model XML file is assumed to be in the /db directory of a Rails app.
30
+ Creation of a model file or scaffolding only occurs if a model file is
31
+ missing. Only tables with relationships drive creation of a model file.
32
+
33
+ === Simple Example
34
+
35
+ Get DBDesigner 4 at http://www.fabforce.net/dbdesigner4/
36
+ Eventually, this will be replaced by MySQL Workbench.
37
+
38
+ Using DBDesigner 4, create two tables: banks and accounts. Add a 1:n
39
+ relationship between the "banks" table and the "accounts" table. Name
40
+ the relationship "has_many :accounts". Open the "accounts" table and
41
+ add the following comment: "[SCAFFOLD]These are the bank accounts."
42
+ Comments can have newlines (/n) in them; dbmodel will break the comments
43
+ into separate lines.
44
+
45
+ DBDesigner can synchronize a model with your database in two ways:
46
+ 1) Output a SQL create script (a DDL file).
47
+ 2) Use the "synchronize" feature which updates your database. Note that
48
+ DBDesigner is an old program that will be re-released as MySQL
49
+ Workbench. For now, though, in order to use the built-in synchronize
50
+ feature, you'll have to establish a MySQL account with an OLD PASSWORD
51
+ (pre-4.1) -- see http://www.billkatz.com/rails_dbmodel
52
+
53
+ Save the database model as "bankmodel". By default, the model is saved
54
+ as an XML file from DBDesigner. Place the XML file in the /db directory
55
+ of your Rails app.
56
+
57
+ To update the Rails model files, simply execute the following:
58
+
59
+ ruby script/generate dbmodel bankmodel.xml
60
+
61
+ The generator will create Bank and Account model files by calling
62
+ the model or scaffold generator, depending on whether a table has
63
+ the [SCAFFOLD] tag in the table comments. If a model file already
64
+ exists, it is NOT overwritten; the file is scanned for the existence
65
+ of the proper relationships, and if they aren't there, they're added.
66
+ Table comments are written only when a file is newly created.
67
+
68
+ === License
69
+
70
+ dbmodel is available under an MIT-style license.
71
+
72
+ :include: MIT-LICENSE
@@ -0,0 +1,69 @@
1
+ NAME
2
+ dbmodel - rails model generator from XML database model file
3
+
4
+ SYNOPSIS
5
+ dbmodel [options] [xml_file_name(s) ...]
6
+
7
+ DESCRIPTION
8
+ The dbmodel generator loads an XML file from a database design tool
9
+ (MySQL DBDesigner 4), generates files you'd normally get by
10
+ "generate model" or "generate scaffold", and inserts comments and
11
+ relationships (e.g., "has_many :foos") into the model files.
12
+
13
+ If no XML files are specified on the command line, dbmodel will
14
+ check if the file "dbmodel.xml" is present in the current directory.
15
+
16
+ While full roundtripping is possible in the future, this version is a
17
+ simplistic proof-of-concept. It assumes you are using the free
18
+ DBDesigner 4. Tables are created using Rails naming conventions and
19
+ relationships are named in Rails fashion. Scaffolding commands can
20
+ be embedded as comments in the tables. Scaffolding commands can
21
+ be embedded as comments in the tables using the format:
22
+ [SCAFFOLD]
23
+ or optionally
24
+ [SCAFFOLD controller-name action1 action2 ...]
25
+
26
+ dbmodel is simplistic and requires convention to bypass configuration.
27
+ Relationships should be labelled habtm, has_many, or has_one. Reciprocal
28
+ relationships for has_many and has_one are auto-generated. The database
29
+ model XML file is assumed to be in the /db directory of a Rails app.
30
+ Creation of a model file or scaffolding only occurs if a model file is
31
+ missing. Only tables with relationships drive creation of a model file.
32
+
33
+ EXAMPLE
34
+ Get DBDesigner 4 at http://www.fabforce.net/dbdesigner4/
35
+ Eventually, this will be replaced by MySQL Workbench.
36
+
37
+ Using DBDesigner 4, create two tables: banks and accounts. Add a 1:n
38
+ relationship between the "banks" table and the "accounts" table. Name
39
+ the relationship "has_many :accounts". Open the "accounts" table and
40
+ add the following comment: "[SCAFFOLD]These are the bank accounts."
41
+ Comments can have newlines (/n) in them; dbmodel will break the comments
42
+ into separate lines.
43
+
44
+ DBDesigner can synchronize a model with your database in two ways:
45
+ 1) Output a SQL create script (a DDL file).
46
+ 2) Use the "synchronize" feature which updates your database. Note that
47
+ DBDesigner is an old program that will be re-released as MySQL
48
+ Workbench. For now, though, in order to use the built-in synchronize
49
+ feature, you'll have to establish a MySQL account with an OLD PASSWORD
50
+ (pre-4.1) -- see http://www.billkatz.com/rails_dbmodel
51
+
52
+ Save the database model as "bankmodel". By default, the model is saved
53
+ as an XML file from DBDesigner. Place the XML file in the /db directory
54
+ of your Rails app.
55
+
56
+ To update the Rails model files, simply execute the following:
57
+
58
+ ruby script/generate dbmodel bankmodel.xml
59
+
60
+ The generator will create Bank and Account model files by calling
61
+ the model or scaffold generator, depending on whether a table has
62
+ the [SCAFFOLD] tag in the table comments. If a model file already
63
+ exists, it is NOT overwritten; the file is scanned for the existence
64
+ of the proper relationships, and if they aren't there, they're added.
65
+ Table comments are written only when a file is newly created.
66
+
67
+ LICENSE
68
+ dbmodel is available under an MIT-style license.
69
+
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ Gem::manage_gems
3
+ require 'rake/gempackagetask'
4
+
5
+ spec = Gem::Specification.new do |s|
6
+ s.name = "dbmodel"
7
+ s.version = "0.1.0"
8
+ s.author = "William T Katz"
9
+ s.email = "bill@billkatz.com"
10
+ s.homepage = "http://www.billkatz.com/rails_dbmodel"
11
+ s.platform = Gem::Platform::RUBY
12
+ s.summary = "A program that generates Rails files from a data model"
13
+ s.files = FileList["[A-Z]*","{bin,lib}/**/*"].exclude("rdoc").to_a
14
+ s.require_path = "lib"
15
+ s.bindir = "bin"
16
+ s.executables = ["dbmodel"]
17
+ s.default_executable = "dbmodel"
18
+ s.autorequire = "dbmodel"
19
+ s.has_rdoc = true
20
+ s.extra_rdoc_files = ["README", "MIT-LICENSE"]
21
+ s.add_dependency("rails", ">= 0.12.0")
22
+ end
23
+
24
+ Rake::GemPackageTask.new(spec) do |pkg|
25
+ pkg.need_tar = true
26
+ end
@@ -0,0 +1,7 @@
1
+ begin
2
+ require 'dbmodel'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ require_gem 'dbmodel'
6
+ end
7
+ DBModelApp.new.run
@@ -0,0 +1,295 @@
1
+ #--
2
+ # Copyright (c) 2005 William T Katz
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ DBMODELVERSION = '0.1.0'
25
+
26
+
27
+ require 'getoptlong'
28
+ require 'rexml/document'
29
+ require 'rails_generator'
30
+ require 'rails_generator/scripts/generate'
31
+
32
+ # A class to handle parsing of table information from a datamodel XML file.
33
+ # Since relationships are defined by table IDs, a hash is constructed using
34
+ # table IDs.
35
+ class Tables
36
+ def initialize
37
+ @table_hash = Hash.new
38
+ end
39
+
40
+ # Adds a table to the hash, where the table is a <tt>REXML::Element</tt> with
41
+ # +ID+, +Tablename+, and +Comments+ attributes
42
+ def add_table(t)
43
+ @table_hash[t.attributes['ID']] = {
44
+ 'name' => t.attributes['Tablename'],
45
+ 'comments' => t.attributes['Comments'],
46
+ 'relationships' => []
47
+ }
48
+ end
49
+
50
+ # Adds a relationship to the hash, where the relationship has source and
51
+ # destination table IDs definied by attributes +SrcTable+ and +DstTable+.
52
+ # The +RelationName+ attribute should be Rails-valid code. To-do: this
53
+ # could be made more robust where we trace the relationships through
54
+ # join tables and don't require any model names in the relationship name.
55
+ def add_relationship(r)
56
+ src_table_id = r.attributes['SrcTable']
57
+ src_table_name = @table_hash[src_table_id]['name']
58
+ dest_table_id = r.attributes['DestTable']
59
+ dest_table_name = @table_hash[dest_table_id]['name']
60
+
61
+ relationship = r.attributes['RelationName']
62
+ if relationship =~ /\s*habtm\s*:(\w+)/
63
+ relationship = " has_and_belongs_to_many :#{$1}"
64
+ else
65
+ # If we are inserting the other side of a relationship (non-habtm),
66
+ # we need to mirror the relationship.
67
+ # NOTE: in the DB model, links should be labeled 'has_one' or 'has_many'
68
+ # not 'belongs_to' since that label is ambiguous 1:1 or 1:n
69
+ if relationship =~ /has_one/ or relationship =~ /has_many/
70
+ @table_hash[dest_table_id]['relationships'] << ' belongs_to :' + src_table_name
71
+ else relationship !~ /has_and_belongs_to_many/
72
+ puts "error: relationships must be labeled 'has_one :x', 'has_many :x', 'habtm :x' or 'has_and_belongs_to_many :x'"
73
+ return
74
+ end
75
+ relationship = ' ' + relationship
76
+ end
77
+ @table_hash[src_table_id]['relationships'] << relationship
78
+ end
79
+
80
+ # Updates the Rails app files to reflect what's in the table hash. The location
81
+ # of the Rails app is gleaned from +xmlfile+, which is assumed to be in /db.
82
+ # If a model file doesn't exist, a <tt>generate model</tt> or <tt>generate scaffold</tt>
83
+ # is called, depending on the presence of a <tt>[SCAFFOLD]</tt> tag in the table
84
+ # comments. Existing files are checked to make sure the relationship
85
+ # is defined. To-do: old relationships aren't removed
86
+ def update_files(xmlfile)
87
+ @table_hash.each do |table_id, table|
88
+ if not table['relationships'].empty?
89
+ add_comments = false
90
+ # If there's no model file, create one via generate scaffold script
91
+ modelfile = File.dirname(xmlfile) + '\..\app\models\\' + table['name'].singularize + '.rb'
92
+ if File.exist?(modelfile)
93
+ puts "Model file (#{table['name'].singularize}) already exists. Skipping generation." if not $silent
94
+ elsif table['comments'] =~ /\[\s*SCAFFOLD\s*(\s.*)*\s*\]/
95
+ cmdline = ['scaffold', table['name'].singularize]
96
+ if not $1.nil?
97
+ $1.split(' ').collect { |arg| cmdline << arg }
98
+ end
99
+ cmdline << '-f'
100
+ puts "Calling generator: #{cmdline.join(' ')}" if not $silent
101
+ Rails::Generator::Scripts::Generate.new.run(cmdline) if not $dryrun
102
+ add_comments = true
103
+ elsif
104
+ cmdline = ['model', table['name'].singularize, '-f']
105
+ puts "Generating model for #{table['name'].singularize}" if not $silent
106
+ Rails::Generator::Scripts::Generate.new.run(cmdline) if not $dryrun
107
+ add_comments = true
108
+ end
109
+
110
+ update_file(modelfile, table_id, add_comments) if not $dryrun
111
+ end
112
+ end
113
+ end
114
+
115
+ #####################################
116
+ private
117
+
118
+ def find_class_declaration_line(code)
119
+ code.each_with_index do |line, index|
120
+ return index if line =~ /class/
121
+ end
122
+ puts "error: couldn't find class to insert relationships"
123
+ return -2
124
+ end
125
+
126
+ def prettify_comments(comment)
127
+ lines = comment.split(/\[\s*SCAFFOLD.*\]/).join.split('\n')
128
+ return lines
129
+ end
130
+
131
+ # Update model file for this table
132
+ def update_file(modelfile, table_id, add_comments)
133
+ begin
134
+ File.open(modelfile, "r+") do |file|
135
+ modelcode = file.readlines
136
+ class_line = find_class_declaration_line(modelcode)
137
+ if class_line >= 0
138
+ # Update the relationships
139
+ @table_hash[table_id]['relationships'].each do |relationship|
140
+ our_relation = Regexp.new(relationship.strip)
141
+ unless modelcode.grep(our_relation).any?
142
+ puts " --> insert#{relationship} (in #{modelfile})" if not $silent
143
+ modelcode.insert(class_line + 1, relationship + "\n")
144
+ end
145
+ end
146
+ # Add comments
147
+ if add_comments and not @table_hash[table_id]['comments'].empty?
148
+ puts " --> modifying comments in #{modelfile}" if not $silent
149
+ lines = prettify_comments(@table_hash[table_id]['comments'])
150
+ lines.reverse.each do |line|
151
+ modelcode.insert(class_line, '# ' + line + "\n")
152
+ end
153
+ end
154
+ # File should always grow with our additions
155
+ file.rewind
156
+ file.puts(modelcode)
157
+ end
158
+ end
159
+ rescue
160
+ puts "\nCouldn't open file (#{modelfile}). Make sure your database "
161
+ puts "model XML files are in the /db directory of your Rails app."
162
+ end
163
+ end
164
+ end
165
+
166
+ ############################################################################
167
+ # DBModel main application object. When invoking +dbmodel+ from the command
168
+ # line, a DBModelApp object is created and run.
169
+ #
170
+ class DBModelApp
171
+
172
+ OPTIONS = [
173
+ ['--dry-run', '-n', GetoptLong::NO_ARGUMENT,
174
+ "Do a dry run without executing actions."],
175
+ ['--help', '-H', GetoptLong::NO_ARGUMENT,
176
+ "Display this help message."],
177
+ ['--quiet', '-q', GetoptLong::NO_ARGUMENT,
178
+ "Do not log messages to standard output."],
179
+ ['--usage', '-h', GetoptLong::NO_ARGUMENT,
180
+ "Display usage."],
181
+ ['--version', '-V', GetoptLong::NO_ARGUMENT,
182
+ "Display the program version."],
183
+ ]
184
+
185
+ # Create a DBModelApp object.
186
+ def initialize
187
+ end
188
+
189
+ # Display the program usage line.
190
+ def usage
191
+ puts "dbmodel {options} [dbmodelfiles ...]"
192
+ end
193
+
194
+ # Display the dbmodel command line help.
195
+ def help
196
+ usage
197
+ puts
198
+ puts "Options are ..."
199
+ puts
200
+ OPTIONS.sort.each do |long, short, mode, desc|
201
+ if mode == GetoptLong::REQUIRED_ARGUMENT
202
+ if desc =~ /\b([A-Z]{2,})\b/
203
+ long = long + "=#{$1}"
204
+ end
205
+ end
206
+ printf " %-20s (%s)\n", long, short
207
+ printf " %s\n", desc
208
+ end
209
+ puts "\nPlease read the rdoc README for more information."
210
+ end
211
+
212
+ # Return a list of the command line options supported by the
213
+ # program.
214
+ def command_line_options
215
+ OPTIONS.collect { |lst| lst[0..-2] }
216
+ end
217
+
218
+ # Do the option defined by +opt+ and +value+.
219
+ def do_option(opt, value)
220
+ case opt
221
+ when '--dry-run'
222
+ $dryrun = true
223
+ when '--help'
224
+ help
225
+ exit
226
+ when '--quiet'
227
+ $silent = true
228
+ when '--usage'
229
+ usage
230
+ exit
231
+ when '--version'
232
+ puts "dbmodel, version #{DBMODELVERSION}"
233
+ exit
234
+ else
235
+ fail "Unknown option: #{opt}"
236
+ end
237
+ end
238
+
239
+ # Read and handle the command line options.
240
+ def handle_options
241
+ opts = GetoptLong.new(*command_line_options)
242
+ opts.each { |opt, value| do_option(opt, value) }
243
+ end
244
+
245
+ # Collect the list of dbmodel filenames on the command line.
246
+ # Default filename is "dbmodel.xml"
247
+ def get_filenames
248
+ filenames = []
249
+ ARGV.each { |arg| filenames << arg if arg !~ /^-+.*/ }
250
+ if filenames.size == 0
251
+ if File.exist?('dbmodel.xml')
252
+ filenames.push("dbmodel.xml")
253
+ else
254
+ puts "\nCould not find any database model XML files."
255
+ end
256
+ end
257
+ filenames
258
+ end
259
+
260
+ # Run the dbmodel app
261
+ def run
262
+ handle_options
263
+ begin
264
+ get_filenames.each do |f|
265
+ require File.dirname(f) + '/../config/environment' # Better way to do this?
266
+ @dbmodelxml = REXML::Document.new(File.open(f))
267
+ # Verify that this is a DBDesigner XML file
268
+ if @dbmodelxml.elements['DBMODEL'].nil?
269
+ puts "\nFile '#{f}' is not a DBDesigner 4 XML file. Skipping..."
270
+ else
271
+ puts "\nReading the datamodel XML (#{f}) from DBDesigner..." if not $silent
272
+ # Create a hash for each table with ID key
273
+ tables = Tables.new
274
+ @dbmodelxml.elements.each("//TABLE") { |t| tables.add_table(t) }
275
+ # Add relationship information to the tables
276
+ @dbmodelxml.elements.each("//RELATION") { |r| tables.add_relationship(r) }
277
+ # Modify the files for each table
278
+ tables.update_files(f)
279
+ end
280
+ end
281
+ rescue Exception => ex
282
+ puts "dbmodel aborted!"
283
+ if ex.message =~ /config\/environment/
284
+ puts "Make sure your datamodel XML file is in the Rails app /db directory\n"
285
+ end
286
+ puts ex.message
287
+ puts ex.backtrace.find {|str| str =~ /\.rb/ } || ""
288
+ exit(1)
289
+ end
290
+ end
291
+ end
292
+
293
+ if __FILE__ == $0 then
294
+ DBModelApp.new.run
295
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.10
3
+ specification_version: 1
4
+ name: dbmodel
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2005-07-25
8
+ summary: A program that generates Rails files from a data model
9
+ require_paths:
10
+ - lib
11
+ email: bill@billkatz.com
12
+ homepage: http://www.billkatz.com/rails_dbmodel
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: dbmodel
16
+ default_executable: dbmodel
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ authors:
28
+ - William T Katz
29
+ files:
30
+ - MIT-LICENSE
31
+ - Rakefile
32
+ - README
33
+ - README.txt
34
+ - bin/dbmodel
35
+ - lib/dbmodel.rb
36
+ test_files: []
37
+ rdoc_options: []
38
+ extra_rdoc_files:
39
+ - README
40
+ - MIT-LICENSE
41
+ executables:
42
+ - dbmodel
43
+ extensions: []
44
+ requirements: []
45
+ dependencies:
46
+ - !ruby/object:Gem::Dependency
47
+ name: rails
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Version::Requirement
50
+ requirements:
51
+ -
52
+ - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 0.12.0
55
+ version: