dbmodel 0.1.0

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.
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: