Created
September 24, 2011 21:56
-
-
Save andynu/1239916 to your computer and use it in GitHub Desktop.
Reverse engineer ActiveRecord definitions by examining an existing (mysql) database.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# encoding: utf-8 | |
# | |
# Reverse engineer ActiveRecord definitions | |
# by examining an existing database. | |
# | |
# This is mysql spesific at the moment. | |
# | |
# % active_record_by_inference -h | |
# Usage: active_record_by_inference.rb [options] [table] [table] ... | |
# -c, --config FILE A rails database.yml [defautl=~/.database.yml] | |
# -o, --output DIRECTORY The directory to write model files into | |
# (otherwise classes are printed to stdout) | |
# -l, --database KEY A key from your database.yml | |
# -h, --help Display this screen | |
# | |
# | |
require 'logger' | |
require 'active_record' | |
class ActiveRecordByInference | |
class Base < ActiveRecord::Base | |
def self.connect_to(connection_hashes, key) | |
self.establish_connection(connection_hashes[key]) | |
end | |
end | |
end | |
class ActiveRecordByInference | |
@@log = Logger.new(STDERR) | |
def initialize(config={}) | |
@config = config | |
database_config_file = config[:config_file] | |
connection_key = config[:database_key] | |
connection_hashes = YAML.load_file(database_config_file) | |
@conn = ActiveRecordByInference::Base.connect_to(connection_hashes,connection_key).connection | |
end | |
def run | |
table_descriptions = {} | |
tables = if @config[:tables] && !@config[:tables].empty? | |
list_tables() & @config[:tables] | |
else | |
list_tables() | |
end | |
tables.each do |table| | |
table_descriptions[table] = desc_table(table) | |
end | |
puts generate_classes(table_descriptions) | |
end | |
def list_tables() | |
list = [] | |
@conn.execute("show tables").each do |row| | |
list << row[0] | |
end | |
list | |
end | |
def desc_table(table) | |
desc = [] | |
@conn.execute("describe #{table}").each do |row| | |
desc << { | |
:column => row[0], | |
:type => row[1], | |
:null => row[2] == "YES", | |
:primary => row[3] == "PRI", | |
:default => row[4], | |
:auto_increment => row[5] == "auto_increment", | |
} | |
end | |
desc | |
end | |
def generate_associations(table_descriptions) | |
associations = {} | |
has_many = {} | |
table_names = table_descriptions.keys.inject({}) do |hash,table| | |
hash[table.pluralize] = table | |
hash[table.singularize] = table | |
hash | |
end | |
ref_keys = [] | |
table_descriptions.each_pair do |table,cols| | |
ref_keys << "#{table.singularize.underscore}_id" | |
end | |
table_descriptions.each_pair do |table,cols| | |
cols.each do |col| | |
column = col[:column] | |
ref_table = column.gsub(/_id$/,'').singularize | |
if ref_keys.include? column | |
associations[table] ||= [] | |
associations[table] << " belongs_to :#{ref_table}" | |
associations[table_names[ref_table]] ||= [] | |
associations[table_names[ref_table]] << " has_many :#{table.pluralize}" | |
end | |
end | |
end | |
associations | |
end | |
def write_file(table,class_src) | |
filename = File.join(@config[:output_dir],"#{table.singularize.underscore}.rb") | |
File.open(filename,'w') do |file| | |
@@log.info " wrote #{filename}" | |
file.puts "# #{filename}" | |
file.puts class_src | |
end | |
end | |
def generate_classes(table_descriptions) | |
associations = generate_associations(table_descriptions) | |
src = "" | |
table_descriptions.each_pair do |table, desc| | |
class_src = generate_class(table,desc, associations[table] || []) | |
write_file(table,class_src) if @config[:output_dir] | |
src << class_src | |
end | |
src | |
end | |
def generate_class(table, desc, associations=[]) | |
class_name = table.camelize.singularize | |
clazz = [] | |
clazz << "# #{table}" | |
clazz << "class #{class_name} < ActiveRecord::Base" | |
clazz << set_table_name(table,desc) | |
clazz << set_primary_key(table,desc) | |
clazz << validations(table,desc) | |
clazz.concat associations | |
clazz << "end\n\n" | |
clazz.compact.join("\n") | |
end | |
def set_table_name(table,desc) | |
unless table == table.pluralize | |
"set_table_name '#{table}'" | |
end | |
end | |
def set_primary_key(table,desc) | |
pk = desc.select{|col| col[:primary]}.map{|col| ":#{col[:column]}"} | |
if pk.size == 1 | |
" set_primary_key #{pk.first}" unless pk.first == ':id' | |
elsif pk.size > 1 | |
" # require 'composite_primary_keys'\n set_primary_keys #{pk.join(', ')}" | |
else | |
nil | |
end | |
end | |
def validations(table,desc) | |
not_null = desc.select{|col| col[:column] != 'id' && !col[:null] }.map{|col| col[:column]}.map{|col| ":#{col}"} | |
unless not_null.empty? | |
" validates_precense_of #{not_null.join(', ')}" | |
else | |
nil | |
end | |
end | |
end | |
if $0 == __FILE__ then | |
require 'optparse' | |
opts = { | |
:config_file => File.join(ENV["HOME"],".database.yml"), | |
:database_key => 'default', | |
:tables => ARGV, | |
} | |
OptionParser.new do |opt| | |
opt.banner = "Usage: active_record_by_inference.rb [options] [table] [table] ..." | |
opt.on( '-c', '--config FILE', 'A rails database.yml [defautl=~/.database.yml]' ) do |config| | |
opts[:config_file] = config | |
end | |
opt.on( '-o', '--output DIRECTORY', "The directory to write model files into \n\t\t\t\t (otherwise classes are printed to stdout)" ) do |output_dir| | |
opts[:output_dir] = output_dir | |
end | |
opt.on( '-l', '--database KEY', 'A key from your database.yml' ) do |database| | |
opts[:database_key] = database | |
end | |
opt.on( '-h', '--help', 'Display this screen' ) do | |
puts opt | |
exit | |
end | |
end.parse! | |
ActiveRecordByInference.new(opts).run() | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I encounter lots of legacy databases. This
is a pretty basic script for bootstrapping
an old database into active record.
It is only observing