rafaelp-dbdesigner_migration_generator 0.1.1

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.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Rafael Lima (http://rafael.adm.br)
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.
@@ -0,0 +1,48 @@
1
+ # DBDesigner Migration Generator
2
+
3
+ ## DESCRIPTION
4
+
5
+ This gem generates ActiveRecord Migration files from a DB Designer 4 xml file.
6
+
7
+ ## REQUIREMENTS
8
+
9
+ * activerecord
10
+
11
+ ## INSTALLATION
12
+
13
+ $ sudo gem sources -a http://gems.github.com (you only have to do this once)
14
+ $ sudo gem install rafaelp-dbdesigner_migration_generator
15
+
16
+ Inset line below in your *config/environments/development.rb*
17
+
18
+ require 'dbdesigner_migration_generator'
19
+
20
+ ## USAGE
21
+
22
+ Save you model in *db/dbdesigner_model.xml*, then run:
23
+
24
+ $ ruby script/generate dbdesigner_migration [MigrationName] [only|except] [table1] [table2] [table3]
25
+
26
+ ### Example 1
27
+
28
+ $ ruby script/generate dbdesigner_migration CompleteDatabase
29
+
30
+ ### Example 2
31
+
32
+ $ ruby script/generate dbdesigner_migration CreateAccountsAndUsers only accounts users
33
+
34
+ ### Example 3
35
+
36
+ $ ruby script/generate dbdesigner_migration CreateOtherTables except accounts users
37
+
38
+ ### Tricks
39
+
40
+ You can put the magical keyword *ignore* on first line of table comments, on your model at DB Designer, to automatically ignore the table on migration generation.
41
+
42
+ ## LICENSE
43
+
44
+ DBDesigner Migration Generator is released under the MIT License.
45
+
46
+ ## AUTHOR
47
+
48
+ [Rafael Lima](http://rafael.adm.br) at [BielSystems](http://bielsystems.com.br) and [Myfreecomm](http://myfreecomm.com.br)
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the dbdesigner_migration_generator plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation for the dbdesigner_migration_generator plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'DbdesignerMigrationGenerator'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
@@ -0,0 +1,366 @@
1
+ require 'rails_generator'
2
+ require 'getoptlong'
3
+ require 'rexml/document'
4
+
5
+ class DbdesignerMigrationGenerator < Rails::Generator::NamedBase
6
+
7
+ attr_accessor :migration_name, :tables, :relations
8
+
9
+ def manifest
10
+ @migration_name = class_name
11
+
12
+ if args.include? "except" and args.include? "only"
13
+ raise "It is not possible to use 'except' and 'only' parameters togheter. Try again."
14
+ end
15
+
16
+ @tables = []
17
+ track = nil
18
+ args.each do |arg|
19
+ @tables << arg unless track.nil?
20
+ arg = arg.to_sym
21
+ track = arg if arg.eql? :except or arg.eql? :only
22
+ end
23
+
24
+ if track.eql? :except
25
+ puts "Ignoring table(s) #{@tables.join(',')}\n"
26
+ DBDesignerMigration::Model.ignore_tables = @tables
27
+ elsif track.eql? :only
28
+ puts "Processing only table(s) #{@tables.join(',')}\n"
29
+ DBDesignerMigration::Model.only_tables = @tables
30
+ end
31
+
32
+ begin
33
+ dbmodel_path = File.join('db', 'dbdesigner_model.xml')
34
+ unless File.exist?(dbmodel_path)
35
+ raise "Could not find any database model in db/dbdesigner_model.xml"
36
+ end
37
+
38
+ xml = REXML::Document.new(File.open(dbmodel_path))
39
+
40
+ # Verify that this is a DBDesigner XML file
41
+ if xml.elements['DBMODEL'].nil? or xml.elements['DBMODEL'].attributes["Version"] != '4.0'
42
+ raise "File '#{dbmodel_path}' is not a DBDesigner 4 XML file. Skipping..."
43
+ else
44
+ puts "Reading the datamodel XML (#{dbmodel_path}) from DBDesigner..." if not $silent
45
+ xml.elements.each("//DATATYPE") { |d| DBDesignerMigration::Model.add_datatype(d) }
46
+ xml.elements.each("//TABLE") { |t| DBDesignerMigration::Model.add_table(t) }
47
+ xml.elements.each("//RELATION") { |r| DBDesignerMigration::Model.add_relation(r) }
48
+ end
49
+ rescue Exception => ex
50
+ puts ex.message
51
+ puts ex.backtrace.find {|str| str =~ /\.rb/ } || ""
52
+ exit(1)
53
+ end
54
+
55
+ @tables = DBDesignerMigration::Model.tables
56
+ @tables.delete_if {|table| table.ignore? }
57
+
58
+ @relations = DBDesignerMigration::Model.relations
59
+ @relations.delete_if {|relation| relation.from_table.ignore? or relation.to_table.ignore? }
60
+
61
+ if @tables.empty? and @relations.empty?
62
+ puts "Nothing to do!"
63
+ exit(0)
64
+ end
65
+
66
+ record do |m|
67
+ m.directory File.join('db')
68
+ m.migration_template 'dbdesigner_migration.rb',"db/migrate", :migration_file_name => "#{file_path}"
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+
75
+ module DBDesignerMigration
76
+
77
+ class Model
78
+ @datatypes = []
79
+ @tables = []
80
+ @relations = []
81
+ @only_tables = nil
82
+ @ignore_tables = nil
83
+
84
+ def self.only_tables=(tables)
85
+ @only_tables = tables
86
+ end
87
+
88
+ def self.ignore_tables=(tables)
89
+ @ignore_tables = tables
90
+ end
91
+
92
+ def self.datatypes
93
+ @datatypes
94
+ end
95
+
96
+ def self.tables
97
+ @tables
98
+ end
99
+
100
+ def self.relations
101
+ @relations
102
+ end
103
+
104
+ def self.add_datatype(xmlobj)
105
+ id = xmlobj.attributes['ID']
106
+ name = xmlobj.attributes['TypeName']
107
+
108
+ if(@datatypes.find {|d| d.id == id})
109
+ raise "Duplicate datatype definition on #{name}"
110
+ end
111
+
112
+ @datatypes << Datatype.new(xmlobj)
113
+ end
114
+
115
+ def self.add_table(xmlobj)
116
+ id = xmlobj.attributes['ID']
117
+ name = xmlobj.attributes['TableName']
118
+
119
+ if(@tables.find {|t| t.id == id})
120
+ raise "Duplicate table definition on #{name}"
121
+ end
122
+
123
+ @tables << Table.new(xmlobj)
124
+ end
125
+
126
+ def self.add_relation(xmlobj)
127
+ id = xmlobj.attributes['ID']
128
+ name = xmlobj.attributes['RelationName']
129
+
130
+ if(@relations.find {|t| t.id == id})
131
+ raise "Duplicate table definition on #{name}"
132
+ end
133
+
134
+ @relations << Relation.new(xmlobj)
135
+ end
136
+
137
+ =begin
138
+ PREPARED TO USE FOR MODELS
139
+
140
+ def self.add_relationship(r)
141
+ src_table = @tables.find {|t| t.id == r.attributes['SrcTable']}
142
+ dest_table = @tables.find {|t| t.id == r.attributes['DestTable']}
143
+
144
+ relationship = r.attributes['RelationName']
145
+ if relationship =~ /\s*habtm\s*:(\w+)/
146
+ relationship = " has_and_belongs_to_many :#{$1}"
147
+ else
148
+ # If we are inserting the other side of a relationship (non-habtm),
149
+ # we need to mirror the relationship.
150
+ # NOTE: in the DB model, links should be labeled 'has_one' or 'has_many'
151
+ # not 'belongs_to' since that label is ambiguous 1:1 or 1:n
152
+ if relationship =~ /has_one/ or relationship =~ /has_many/
153
+ dest_table.relationships << ' belongs_to :' + src_table.name
154
+ else relationship !~ /has_and_belongs_to_many/
155
+ puts "error: relationships must be labeled 'has_one :x', 'has_many :x', 'habtm :x' or 'has_and_belongs_to_many :x'"
156
+ return
157
+ end
158
+ relationship = ' ' + relationship
159
+ end
160
+ src_table.relationships << relationship
161
+ end
162
+ =end
163
+ def self.format_options(options)
164
+ return if options.empty?
165
+
166
+ formatted = ""
167
+ options.each do |k,v|
168
+ if v.is_a? Symbol
169
+ formatted << ", :#{k} => :#{v}"
170
+ elsif v.is_a? String
171
+ formatted << ", :#{k} => '#{v}'"
172
+ else
173
+ formatted << ", :#{k} => #{v}"
174
+ end
175
+ end
176
+
177
+ formatted
178
+ end
179
+
180
+ protected
181
+ def self.include_table?(table_name)
182
+ return ((!@only_tables.nil? and @only_tables.include? table_name) or (@only_tables.nil? and @ignore_tables.nil?) or (!@ignore_tables.nil? and !@ignore_tables.include? table_name))
183
+ end
184
+ end
185
+
186
+ class Datatype
187
+ attr_accessor :id, :name, :description, :physical
188
+
189
+ def initialize(xmlobj)
190
+ @id = xmlobj.attributes['ID']
191
+ @name = @physical = xmlobj.attributes['TypeName'].downcase
192
+ @description = xmlobj.attributes['Description']
193
+ @physical = xmlobj.attributes['PhysicalTypeName'].downcase if not xmlobj.attributes['PhysicalTypeName'].empty?
194
+ end
195
+ end
196
+
197
+ class Table
198
+ attr_accessor :id, :name, :comments, :columns, :indexes, :relationships, :options, :references
199
+
200
+ alias_method :fields, :columns
201
+
202
+ def initialize(xmlobj)
203
+ @id = xmlobj.attributes['ID']
204
+ @name = xmlobj.attributes['Tablename']
205
+ @comments = xmlobj.attributes['Comments'].split("\\n")
206
+ @columns = []
207
+ @indexes = []
208
+ @relationships = []
209
+ @references = []
210
+ @options = {}
211
+ @process = ((Model.include_table? @name) and (@comments.empty? or @comments.first.downcase.strip != "ignore"))
212
+
213
+ if @name == @name.singularize
214
+ puts "Warning: table #{@name} is not in plural\n"
215
+ end
216
+
217
+ xmlobj.elements.each("COLUMNS/COLUMN") { |c| self.add_column(c) }
218
+ xmlobj.elements.each("INDICES/INDEX") { |i| self.add_index(i) }
219
+ end
220
+
221
+ def process?
222
+ @process
223
+ end
224
+
225
+ def ignore?
226
+ !@process
227
+ end
228
+
229
+ def add_column(xmlobj)
230
+ id = xmlobj.attributes['ID']
231
+ name = xmlobj.attributes['ColName']
232
+ if("id" == name.downcase)
233
+ return
234
+ end
235
+
236
+ if(@columns.find {|c| c.id == id})
237
+ raise "Duplicate column definition on #{self.name} #{name}"
238
+ end
239
+
240
+ @columns << Column.new(xmlobj)
241
+ end
242
+
243
+ def add_index(xmlobj)
244
+ id = xmlobj.attributes['ID']
245
+ name = xmlobj.attributes['IndexName']
246
+
247
+ xmlobj.elements.each("INDEXCOLUMNS/INDEXCOLUMN") { |i|
248
+ if(self.columns.find {|c| c.id == i.attributes['idColumn']}.nil?)
249
+ return
250
+ end
251
+ }
252
+
253
+ if(@indexes.find {|i| i.id == id})
254
+ raise "Duplicate index definition on #{self.name} #{name} (#{id})"
255
+ end
256
+
257
+ @indexes << Index.new(self, xmlobj)
258
+ end
259
+
260
+ def add_references(reference)
261
+ @references << reference
262
+ end
263
+
264
+ end
265
+
266
+ class Column
267
+ attr_accessor :id, :name, :datatype, :params, :notnull, :default, :comments, :options
268
+
269
+ def initialize(xmlobj)
270
+ @id = xmlobj.attributes['ID']
271
+ @name = xmlobj.attributes['ColName']
272
+ @datatype = Model.datatypes.find {|c| c.id == xmlobj.attributes['idDatatype']}.physical
273
+ @params = xmlobj.attributes['DatatypeParams']
274
+ @notnull = xmlobj.attributes['NotNull']
275
+ @default = xmlobj.attributes['DefaultValue']
276
+ @comments = xmlobj.attributes['Comments']
277
+ @comments = "# #{@comments.split("\\n").join(', ')}" unless @comments.empty?
278
+
279
+ options = {}
280
+ options['default'] = @default unless @default.empty?
281
+ options['null'] = ("1" == @notnull) ? false : true
282
+ if not @params.empty?
283
+ if float = /\(([0-9]+),([0-9]+)\)/.match(@params)
284
+ options['precision'] = float[1].to_i
285
+ options['scale'] = float[2].to_i
286
+ else
287
+ options['limit'] = /\(([0-9]*)\)/.match(@params)[1].to_i
288
+ end
289
+ end
290
+ # @options['limit'] = (eval(@params) rescue nil) if not @params.empty?
291
+
292
+ @options = Model.format_options(options)
293
+
294
+ end
295
+
296
+ end
297
+
298
+ class Index
299
+ attr_accessor :id, :table, :name, :columns, :unique, :options
300
+
301
+ def initialize(table, xmlobj)
302
+ @id = xmlobj.attributes['ID']
303
+ @name = xmlobj.attributes['IndexName']
304
+ @table = table.name
305
+ columns = Array.new
306
+ xmlobj.elements.each("INDEXCOLUMNS/INDEXCOLUMN") { |i|
307
+ columns << table.columns.find {|c| c.id == i.attributes['idColumn']}.name
308
+ }
309
+ @columns = "[:#{columns.join(',:')}]" unless columns.nil?
310
+ @unique = xmlobj.attributes['IndexKind'] == "2"
311
+ if @unique
312
+ options = {}
313
+ options['unique'] = @unique
314
+ @options = Model.format_options(options)
315
+ end
316
+ end
317
+ end
318
+
319
+ class Relation
320
+ attr_accessor :id, :name, :from_table, :from_column, :to_table, :to_column, :options
321
+
322
+ def initialize(xmlobj)
323
+ # DBDesigner codes
324
+ # 0 = restrict
325
+ # 1 = cascade
326
+ # 2 = set null
327
+ # 3 = no action
328
+ # 4 = set default
329
+ @dbdesigner_codes = ['restrict','cascade','set null','no action','set default']
330
+
331
+ @id = xmlobj.attributes['ID']
332
+ @name = xmlobj.attributes['RelationName'].downcase
333
+ @from_table = Model.tables.find {|t| t.id == xmlobj.attributes['DestTable']}
334
+ @to_table = Model.tables.find {|t| t.id == xmlobj.attributes['SrcTable']}
335
+ fields = xmlobj.attributes['FKFields'].strip.split("=")
336
+ @from_column = fields[1].match(/([a-zA-Z_-])*/).to_s
337
+ @to_column = fields[0].match(/([a-zA-Z_-])*/).to_s
338
+
339
+ options = {}
340
+
341
+ on_delete = xmlobj.attributes['RefDef'].match(/OnDelete=([0-4]{1})/)
342
+ if on_delete
343
+ @on_delete = @dbdesigner_codes[on_delete[1].to_i]
344
+ options['on_delete'] = @on_delete unless on_delete[1] == '3'
345
+ end
346
+
347
+ on_update = xmlobj.attributes['RefDef'].match(/OnUpdate=([0-4]{1})/)
348
+ if on_update
349
+ @on_update = @dbdesigner_codes[on_update[1].to_i]
350
+ options['on_update'] = @on_update unless on_update[1] == '3'
351
+ end
352
+ @options = Model.format_options(options)
353
+
354
+ if(@from_table.process? and @to_table.process?)
355
+ if (@from_column == 'parent_id' and @from_table.name != @to_table.name) or
356
+ (@from_column != 'parent_id' and @from_column != "#{@to_table.name.singularize}_#{@to_column}")
357
+ puts "Warning: foreign_key #{@from_table.name}.#{@from_column} => #{@to_table.name}.#{@to_column}\n"
358
+ end
359
+ end
360
+
361
+ @from_table.add_references(@to_table) if @to_column.eql? 'id'
362
+ end
363
+ end
364
+
365
+ end
366
+
@@ -0,0 +1,43 @@
1
+ require 'postgresql_migrations'
2
+
3
+ <% tables.each do |table| -%>
4
+ # script/generate rspec_model -c --skip-migration <%= table.name.camelize %> <% table.references.each do |table| -%><%= table.name %>:reference <% end -%> <% table.fields.each do |field| -%><%= "#{field.name}:#{field.datatype}" %> <% end -%>
5
+
6
+ <% end -%>
7
+
8
+ class <%= migration_name.underscore.camelize %> < ActiveRecord::Migration
9
+ def self.up<% tables.each do |table| -%>
10
+ <% table.comments.each do |comment| -%>
11
+ <%= "\# #{comment}" %>
12
+ <% end -%>
13
+
14
+ create_table "<%= table.name %>"<% table.options.each do |k,v| %>, :<%= k %> => <%= v %><% end %>, :force => true do |t|
15
+ <% table.fields.each do |field| -%>
16
+ t.<%= field.datatype %> "<%= field.name %>" <%= field.options %> <%= field.comments %>
17
+ <% end -%>
18
+ t.timestamps
19
+ end
20
+
21
+ <% table.indexes.sort_by {|index| index.table.to_s}.each do |index| -%>
22
+ <%= "add_index :#{index.table}, #{index.columns} #{index.options}" %>
23
+ <% end -%>
24
+ <% end -%>
25
+
26
+ <% relations.each do |relation| -%>
27
+ <%= "add_foreign_key :#{relation.from_table.name}, :#{relation.from_column}, :#{relation.to_table.name}, :#{relation.to_column} #{relation.options}" %>
28
+ <% end -%>
29
+ end
30
+
31
+ def self.down
32
+ <% relations.each do |relation| -%>
33
+ <%= "remove_foreign_key :#{relation.from_table.name}, :#{relation.from_column}" %>
34
+ <% end -%>
35
+ <% tables.sort_by {|table| table.name.to_s }.each do |table| -%>
36
+ <% table.indexes.sort_by {|index| index.table.to_s}.each do |index| -%>
37
+ <%= "remove_index :#{index.table}, #{index.columns}" %>
38
+ <% end -%>
39
+ <%= "drop_table :#{table.name}" %>
40
+ <% end -%>
41
+ end
42
+ end
43
+
@@ -0,0 +1,8 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class DbdesignerGeneratorTest < Test::Unit::TestCase
4
+ # Replace this with your real tests.
5
+ def test_this_plugin
6
+ flunk
7
+ end
8
+ end
@@ -0,0 +1,4 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'mocha'
4
+ require File.dirname(__FILE__) + '/../lib/dbdesigner_migration_generator'
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rafaelp-dbdesigner_migration_generator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Rafael Lima
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-19 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">"
21
+ - !ruby/object:Gem::Version
22
+ version: 0.0.0
23
+ version:
24
+ description:
25
+ email: contato@rafael.adm.br
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - README.mkdn
32
+ files:
33
+ - MIT-LICENSE
34
+ - Rakefile
35
+ - README.mkdn
36
+ - dbdesigner_migration_generator.rb
37
+ - templates/dbdesigner_migration.rb
38
+ has_rdoc: true
39
+ homepage: http://rafael.adm.br
40
+ post_install_message:
41
+ rdoc_options: []
42
+
43
+ require_paths:
44
+ - .
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ requirements: []
58
+
59
+ rubyforge_project:
60
+ rubygems_version: 1.2.0
61
+ signing_key:
62
+ specification_version: 2
63
+ summary: Generates ActiveRecord Migration files from a DB Designer 4 xml file.
64
+ test_files:
65
+ - test/test_helper.rb
66
+ - test/dbdesigner_migration_generator_test.rb