schema_generator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,41 @@
1
+ Schema Generator v0.1.0
2
+ by Scott Laird, scott@sigkill.org
3
+ http://scottstuff.net
4
+
5
+ This is a SQL schema generator for Rails. It slurps up all of the migration files in db/migrate and turns them into a single SQL schema file for each supported database type. The schema files contains table definitions, indexes, and any seed data created by Model.create() calls in the migrations.
6
+
7
+ Supported databases:
8
+ * MySQL
9
+ * PostgreSQL
10
+ * SQLite
11
+
12
+ Usage
13
+ -----
14
+
15
+ To use, run './scripts/generate schema' on any Rails project. This will produce three files:
16
+
17
+ * db/schema.mysql.sql
18
+ * db/schema.postgresql.sql
19
+ * db/schema.sqlite.sql
20
+
21
+ If these files already exist, then Rails will prompt you before overwriting them.
22
+
23
+ If your Rails project already has migrations to build its entire schema from scratch, then this will work right off the bat. If not, you may wish to create a db/migrate/0_initial_schema.rb migration and use it to create your initial schema using the usual migration create_table methods.
24
+
25
+ You can see an example in Typo (http://typo.leetsoft.com). This generator was written to reduce the pain involved in creating new migrations for Typo, because each migration required an equivalent change to 3 different hand-edited static schema files, which was usually an error-prone process, because no one user tests all three DB types. Now we can just use './script/generate schema' and be done with it.
26
+
27
+
28
+ Supported Migration Actions
29
+ ---------------------------
30
+
31
+ This generator currently supports create_table, add_column, drop_column, add_index, drop_index, and *.create calls. Any model find calls will immediately return an empty array; this allows data migration blocks in migration files to be safely ignored.
32
+
33
+ Column change operations aren't currently supported, but that's simply because I don't have any good test cases. The code needed is probably only 5 lines long.
34
+
35
+
36
+ Debugging and Troubleshooting
37
+ -----------------------------
38
+
39
+ This generator reaches deep into the guts of Rails; it has to make several changes to the DB drivers and ActiveRecord::Base in order to work. This means that it may have issues with future Rails releases. It has been tested with 0.13.1. All of these changes are made on the fly using open classes, so installing this generator will not break any outside code--the modifications are only active while the generator is running.
40
+
41
+ If you have any other problems, please mail me at scott@sigkill.org or post a comment on http://scottstuff.net/. If there is enough interest, then I'll start a schema generator mailing list.
@@ -0,0 +1,312 @@
1
+ require 'pathname'
2
+
3
+ class SchemaGenerator < Rails::Generator::Base
4
+ def initialize(*runtime_args)
5
+ super(*runtime_args)
6
+
7
+ migration_classes = Array.new
8
+
9
+ migration_0 = load_migration(0)
10
+ if(migration_0)
11
+ migration_classes.push migration_0
12
+ end
13
+
14
+ migration_number = 1
15
+ migration = true
16
+
17
+ while migration do
18
+ migration = load_migration(migration_number)
19
+ if(migration)
20
+ migration_classes.push migration
21
+ DBMigrator::Database.version = migration_number
22
+ end
23
+ migration_number += 1
24
+ end
25
+
26
+ STDERR.puts "Found #{migration_classes.size} migration classes"
27
+ DBMigrator::Database.disable_db
28
+
29
+ migration_classes.each do |mclass|
30
+ STDERR.puts "Starting migration for #{mclass}"
31
+ mclass.up
32
+ end
33
+
34
+ STDERR.puts "Migrations complete."
35
+ STDERR.puts " Tables found: #{DBMigrator::Database.tables.size}"
36
+ STDERR.puts " Indexes found: #{DBMigrator::Database.indexes.size}"
37
+ STDERR.puts " Records found: #{DBMigrator::Database.data.size}"
38
+ end
39
+
40
+ def load_migration(migration_number)
41
+ globname = "db/migrate/#{migration_number}_*.rb"
42
+ file = Pathname.glob(globname).to_a.first.to_s
43
+ return nil unless file =~/\.rb$/
44
+
45
+ begin
46
+ file =~ %r{^(db/migrate/#{migration_number}_(.*)).rb$}
47
+ include_name = $1.to_s
48
+ class_base_name = $2.camelize
49
+
50
+ require include_name
51
+ return class_base_name.constantize
52
+ end
53
+ end
54
+
55
+ def manifest
56
+ record do |m|
57
+ m.directory File.join('db')
58
+
59
+ m.template 'schema.postgresql.sql', File.join('db', "schema.postgresql.sql")
60
+ m.template 'schema.mysql.sql', File.join('db', "schema.mysql.sql")
61
+ m.template 'schema.sqlite.sql', File.join('db', "schema.sqlite.sql")
62
+ end
63
+ end
64
+ end
65
+
66
+ module ActiveRecord
67
+ class Base
68
+ alias_method :old_save, :save
69
+
70
+ def dbmigrate_save
71
+ end
72
+
73
+ class << self
74
+ alias_method :old_find, :find
75
+ alias_method :old_create, :create
76
+
77
+ def dbmigrate_find(*args)
78
+ []
79
+ end
80
+
81
+ def dbmigrate_create(attributes = nil)
82
+ DBMigrator::Database.new_data(self,attributes)
83
+ end
84
+
85
+ end
86
+ end
87
+
88
+ class Migration
89
+ def self.create_table(name,options={},&block)
90
+ if(DBMigrator::Database.tables.find {|t| t.name == name})
91
+ raise "Duplicate table definition on #{name}"
92
+ end
93
+ table = DBMigrator::Table.new(name)
94
+ table.options = options
95
+ DBMigrator::Database.tables.push(table)
96
+ block.call(table)
97
+ end
98
+
99
+ def self.drop_table(name)
100
+ DBMigrator::Database.tables.delete_if {|t| t.name == name}
101
+ end
102
+
103
+ def self.add_column(tablename,name,type,options={})
104
+ table=DBMigrator::Database.tables.find{|t| t.name == tablename}
105
+ if(table.fields.find{|f| f.name == name})
106
+ raise "Duplicate field definition for #{tablename}.#{name}"
107
+ end
108
+ table.column(name,type,options)
109
+ end
110
+
111
+ def self.remove_column(tablename,name)
112
+ table = DBMigrator::Database.tables.find{|t| t.name == tablename}
113
+ field = table.fields.delete_if {|f| f.name == name}
114
+ end
115
+
116
+ def self.add_index(tablename,name,options={})
117
+ if(DBMigrator::Database.indexes.find {|i| i.table == tablename and i.name == name})
118
+ return # duplicate
119
+ end
120
+
121
+ index = DBMigrator::Index.new
122
+ index.table = tablename
123
+ index.name = name
124
+ DBMigrator::Database.indexes.push index
125
+ end
126
+
127
+ def self.remove_index(tablename,name)
128
+ DBMigrator::Database.indexes.delete_if {|i| i.table == tablename and i.name == name}
129
+ end
130
+ end
131
+ end
132
+
133
+ module DBMigrator
134
+ class Table
135
+ attr_accessor :name, :options, :fields
136
+
137
+ def initialize(name)
138
+ @name = name
139
+ @fields = Array.new
140
+ end
141
+
142
+ def column(name,type,options={})
143
+ f = Field.new
144
+ f.name = name
145
+ f.type = type
146
+ f.options = options
147
+ @fields.push(f)
148
+ end
149
+ end
150
+
151
+ class Index
152
+ attr_accessor :table, :name
153
+ end
154
+
155
+ class Field
156
+ attr_accessor :table, :name, :type, :options
157
+ end
158
+
159
+ class Data
160
+ attr_accessor :table, :data
161
+
162
+ def initialize(tablename, data)
163
+ @table=tablename
164
+ @data=data
165
+ end
166
+ end
167
+
168
+ class Database
169
+ cattr_accessor :tables, :indexes, :data, :version
170
+
171
+ @@tables = Array.new
172
+ @@indexes = Array.new
173
+ @@data = Array.new
174
+ @@version = 1
175
+
176
+ def self.new_data(tablename,data)
177
+ @@data.push(Data.new(tablename,data))
178
+ end
179
+
180
+ def self.execute(sql,name)
181
+ if(sql =~ /create table/i)
182
+ @@commandstring += reformat_create_table(sql)
183
+ else
184
+ @@commandstring += sql
185
+ @@commandstring += ";\n"
186
+ end
187
+ end
188
+
189
+ def self.dbdriver
190
+ @@dbdriver
191
+ end
192
+
193
+ def self.enable_db
194
+ class << ActiveRecord::Base
195
+ alias find old_find
196
+ alias create old_create
197
+ end
198
+
199
+ ActiveRecord::Base.class_eval 'alias save old_save'
200
+ end
201
+
202
+ def self.disable_db
203
+ class << ActiveRecord::Base
204
+ alias find dbmigrate_find
205
+ alias create dbmigrate_create
206
+ end
207
+ ActiveRecord::Base.class_eval 'alias save dbmigrate_save'
208
+ end
209
+
210
+ def self.dump(databasename)
211
+ @@commandstring=''
212
+ case databasename
213
+ when 'postgresql'
214
+ @@dbdriver = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.new(nil)
215
+ when 'mysql'
216
+ @@dbdriver = ActiveRecord::ConnectionAdapters::MysqlAdapter.new(nil,nil)
217
+ when 'sqlite'
218
+ @@dbdriver = ActiveRecord::ConnectionAdapters::SQLiteAdapter.new(nil)
219
+ else
220
+ raise 'Unknown driver'
221
+ end
222
+
223
+ class << @@dbdriver
224
+ def execute(sql,name = nil)
225
+ DBMigrator::Database.execute(sql,name)
226
+ end
227
+
228
+ def columns(tablename,name = nil)
229
+ table = DBMigrator::Database.tables.find {|t| t.name.to_s == tablename}
230
+ table.fields.collect {|f| ActiveRecord::ConnectionAdapters::Column.new(f.name.to_s, nil, f.type)}
231
+ end
232
+
233
+ def insert(sql, name = nil, pk = nil, id_value = nil)
234
+ execute(sql, name = nil)
235
+ end
236
+
237
+ def rollback_db_transaction; end
238
+ def begin_db_transaction; end
239
+ def commit_db_transaction; end
240
+
241
+ # This is for the schema code, so we don't have security to worry about
242
+ # This implementation might be attackable, but it won't generally fail
243
+ # on reasonable strings.
244
+ def quote_string(string)
245
+ string.gsub(/\'/,"''")
246
+ end
247
+ end
248
+
249
+ @@commandstring += "-- tables \n\n"
250
+
251
+ tables.sort_by {|t| t.name.to_s}.each do |table|
252
+ dbdriver.create_table table.name, table.options do |t|
253
+ table.fields.each do |f|
254
+ t.column f.name, f.type, f.options
255
+ end
256
+ end
257
+ end
258
+
259
+ @@commandstring += "\n-- indexes \n\n" if indexes.size > 0
260
+
261
+ indexes.sort_by {|t| t.table.to_s}.each do |index|
262
+ dbdriver.add_index index.table, index.name.to_s
263
+ end
264
+
265
+ class << ActiveRecord::Base
266
+ def retrieve_connection
267
+ DBMigrator::Database.dbdriver
268
+ end
269
+ end
270
+
271
+ enable_db
272
+
273
+ ActiveRecord::Base.class_eval do
274
+ def attributes_from_column_definition
275
+ tables = DBMigrator::Database.tables
276
+ table = tables.find {|t| t.name.to_s == self.class.table_name}
277
+ table.fields.inject({}) do |a,f|
278
+ a[f.name.to_s]=nil
279
+ a
280
+ end
281
+ end
282
+ end
283
+
284
+ @@commandstring += "\n-- data \n\n" if @@data.size > 0
285
+
286
+ @@data.each do |data|
287
+ data.table.create(data.data)
288
+ end
289
+
290
+ @@commandstring += "\n-- schema version meta-info \n\n"
291
+
292
+ dbdriver.create_table :schema_info, :id => false do |t|
293
+ t.column :version, :integer
294
+ end
295
+
296
+ dbdriver.execute "insert into schema_info (version) values (#{version})"
297
+
298
+ disable_db
299
+ @@commandstring
300
+ end
301
+
302
+ def self.reformat_create_table(sql)
303
+ sql.gsub(/^(create table [^(]+\()/i,"\\1\n ").
304
+ gsub(/, /,",\n ").
305
+ gsub(/\) (ENGINE.*)?$\Z/,"\n) \\1;\n\n").
306
+ gsub(/\) ;$/,');')
307
+ end
308
+ end
309
+
310
+ end
311
+
312
+ $schema_generator = true
@@ -0,0 +1,8 @@
1
+ -- This file is autogenerated by the Rail schema generator, using
2
+ -- the schema defined in db/migration/*.rb
3
+ --
4
+ -- Do not edit this file. Instead, add a new migration using
5
+ -- ./script/generate migration <name>, and then run
6
+ -- ./script/generate schema
7
+
8
+ <%= DBMigrator::Database.dump('mysql') %>
@@ -0,0 +1,8 @@
1
+ -- This file is autogenerated by the Rail schema generator, using
2
+ -- the schema defined in db/migration/*.rb
3
+ --
4
+ -- Do not edit this file. Instead, add a new migration using
5
+ -- ./script/generate migration <name>, and then run
6
+ -- ./script/generate schema
7
+
8
+ <%= DBMigrator::Database.dump('postgresql') %>
@@ -0,0 +1,8 @@
1
+ -- This file is autogenerated by the Rail schema generator, using
2
+ -- the schema defined in db/migration/*.rb
3
+ --
4
+ -- Do not edit this file. Instead, add a new migration using
5
+ -- ./script/generate migration <name>, and then run
6
+ -- ./script/generate schema
7
+
8
+ <%= DBMigrator::Database.dump('sqlite') %>
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: schema_generator
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2005-09-02 00:00:00 -07:00
8
+ summary: "The rails schema generator generates complete SQL schemas from a collection of
9
+ rails migrations. It currently produces one schema file each for MySQL,
10
+ PostgreSQL, and SQLite."
11
+ require_paths:
12
+ - lib
13
+ email: scott@sigkill.org
14
+ homepage: http://scottstuff.net
15
+ rubyforge_project:
16
+ description:
17
+ autorequire: schema_generator
18
+ default_executable:
19
+ bindir: bin
20
+ has_rdoc: false
21
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
22
+ requirements:
23
+ -
24
+ - ">"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.0.0
27
+ version:
28
+ platform: ruby
29
+ signing_key:
30
+ cert_chain:
31
+ authors:
32
+ - Scott Laird
33
+ files:
34
+ - templates/schema.mysql.sql
35
+ - templates/schema.postgresql.sql
36
+ - templates/schema.sqlite.sql
37
+ - schema_generator.rb
38
+ - README
39
+ test_files: []
40
+ rdoc_options: []
41
+ extra_rdoc_files: []
42
+ executables: []
43
+ extensions: []
44
+ requirements: []
45
+ dependencies: []