# A Class that reads and parses a DDL file # currently expects a postgresql DDL generated from # rake db_structure_dump # It currently only extracts # the table names # the associations and foreign keys # the not null constraints # # TODO # require 'yaml' # # the assocs.yml file is expected in the same directory as the script is run in # it overides the association for specific tables # format is yaml, with the referenced table name as the key of a hash of arrays of properties # the :assoc property is the association, the :ref property id the referencing table # the :column is the foreign key in that table # habtm associations need to be specified for both tables and the :ref is the name of the join table # not the table being joined. # specifying an empty array will cuase that table to be ignored in a way that a model will not be generated for it # # example yaml file... # extable1: # - {:assoc: has_one, :ref: extable2, :column: extable1_id} # - {:assoc: has_one, :ref: extable3, :column: extable1_id} # - {:assoc: habtm, :ref: extable1_extable2, :column: extable1_id} # extable2: # - {:assoc: habtm, :ref: extable1_extable2, :column: extable2_id} # ignoreme: [] # class DDL class Table attr_accessor :name, :associations, :columns, :validations class Association attr_accessor :column, :ref_table, :type def initialize(col, ref, type= "") @column= col @ref_table= ref @type= type end def to_s "#{@type}: #{@ref_table}.#{@column}" end end def initialize(name) @associations= [] @columns= {} @validations= [] @name= name end def add_column(name, type= "unknown") @columns[name]= type end def add_association(col, ref, type= "") @associations << Association.new(col, ref, type) end def add_validation(v) @validations << v end end def initialize(fn) @tables= {} begin @assoc_override= YAML.load_file('assocs.yml') puts "Found assocs.yml" if($DEBUG) rescue @assoc_override= {} puts "No assocs.yml found" if($DEBUG) end parse_tables(fn) parse_associations(fn) #parse_uniqueness(fn) end def each @tables.each_value { |o| yield(o) } self end def parse_tables(fn) File.open(fn) do |f| in_table= false cur_table= nil f.each_line do |l| if not in_table m= l.match(/CREATE TABLE (\w+)\s+\(/) unless m.nil? name= m[1] # see if there is an override to ignore this table aoa= @assoc_override[name] if aoa.nil? || !aoa.empty? puts "Found table #{name}" if($DEBUG) t= Table.new(name) @tables[name]= t cur_table= t in_table= true else puts "Ignoring table #{name}" if($DEBUG) end end else if l =~ /\)\s*;/ in_table= false next end m= l.match(/^\s*(\w+)/) unless m.nil? column= m[1] next if column == 'id' # don't try to validate the id cur_table.add_column(column) if l =~ /not null/i cur_table.add_validation("validates_presence_of :#{column}") end if l =~ /unique/i cur_table.add_validation("validates_uniqueness_of :#{column}") end end end end end end def parse_associations(fn) ddl= File.read(fn) ddl.scan(/ALTER TABLE ONLY (\w+)\n\s+ADD CONSTRAINT .+ FOREIGN KEY \((\w+)\) REFERENCES (\w+)\(id\)/) do |tablesrc, col, tabledest| puts "Found relation in table #{tablesrc} column #{col} referencing table #{tabledest}" if($DEBUG) t= @tables[tabledest] raise "table #{tabledest} not found" if t.nil? assoc_type= "has_many" # see if there is an override for this association aoa= @assoc_override[tabledest] unless aoa.nil? || aoa.empty? aoa.each do |ao| if ao[:ref] == tablesrc && ao[:column] == col assoc_type= ao[:assoc] if assoc_type == 'habtm' # need to change the source table name tablesrc= tablesrc.sub(tabledest, "").sub(/^_/, "").sub(/_$/, "") end break end end end t.add_association(col, tablesrc, assoc_type) end end # This version does't handle multiple columns as being unique def parse_uniqueness(fn) ddl= File.read(fn) ddl.scan(/ALTER TABLE ONLY (\w+)\n\s+ADD CONSTRAINT .+ UNIQUE \(([\w,\s]+)\)/) do |tablesrc, col| puts "Found unique in table #{tablesrc} column #{col}" if($DEBUG) t= @tables[tablesrc] raise "table #{tablesrc} not found" if t.nil? a= col.split(',') cols= a.collect { |e| ":#{e.strip}" } t.add_validation("validate_uniqueness_of (#{cols.join(',')})") end end end if __FILE__ == $0 then ddl= DDL.new(ARGV[0]) ddl.each do |d| puts "Table= #{d.name}\n\tassociations= #{d.associations.join(', ')}\n\tvalidations= #{d.validations.join(', ')}" end end