slightly modified http://repo.pragprog.com/svn/Public/plugins/annotate_models/lib/annotate_models.rb to include short declarations for model associations.
require "config/environment"
MODEL_DIR = File.join(RAILS_ROOT, "app/models")
FIXTURE_DIR = File.join(RAILS_ROOT, "test/fixtures")
module AnnotateModels
PREFIX = "== Schema Information"
def self.quote(value)
case value
when NilClass then "NULL"
when TrueClass then "TRUE"
when FalseClass then "FALSE"
when Float, Fixnum, Bignum then value.to_s
when BigDecimal then value.to_s('F')
else
value.inspect
end
end
def self.get_schema_info(klass, header)
info = "# == Model #{klass}\n# #{header}\n#\n"
info << "# Table name: #{klass.table_name}\n#\n"
max_size = klass.column_names.collect{|name| name.size}.max + 1
klass.columns.each do |col|
attrs = []
attrs << "default(#{quote(col.default)})" if col.default
attrs << "not null" unless col.null
attrs << "primary key" if col.name == klass.primary_key
col_type = col.type.to_s
if col_type == "decimal"
col_type << "(#{col.precision}, #{col.scale})"
else
col_type << "(#{col.limit})" if col.limit
end
info << sprintf("# %-#{max_size}.#{max_size}s:%-15.15s %s\n", col.name, col_type, attrs.join(", "))
end
assoc_list = klass.reflect_on_all_associations.sort {|x,y| d = x.class_name.to_s <=> y.class_name.to_s; c = d == 0 ? x.macro.to_s <=> y.macro.to_s : d; c == 0 ? x.name.to_s <=> y.name.to_s : c }
unless assoc_list.empty?
info << "#\n# == Associations\n"
assoc_list.each do |assoc|
ao = assoc.options.dup
ao.delete(:class_name)
line = "# * " +sprintf("<tt>%-25s :%-25s (%s) %s</tt>", assoc.macro, assoc.name, assoc.class_name, ao.empty? ? '' : ao.inspect)
info << line << "\n"
end
end
info << "#\n\n"
end
def self.annotate_one_file(file_name, info_block)
if File.exist?(file_name)
content = File.read(file_name)
content.sub!(/^# #{PREFIX}.*?\n(#.*\n)*\n/, '')
File.open(file_name, "w") { |f| f.puts info_block + content }
end
end
def self.annotate(klass, header)
info = get_schema_info(klass, header)
model_file_name = File.join(MODEL_DIR, klass.name.underscore + ".rb")
annotate_one_file(model_file_name, info)
fixture_file_name = File.join(FIXTURE_DIR, klass.table_name + ".yml")
annotate_one_file(fixture_file_name, info)
end
def self.get_model_names
models = ARGV.dup
models.shift
if models.empty?
Dir.chdir(MODEL_DIR) do
models = Dir["**/*.rb"]
end
end
models
end
def self.do_annotations
header = PREFIX.dup
version = ActiveRecord::Migrator.current_version rescue 0
if version > 0
header << "\n# Schema version: #{version}"
end
self.get_model_names.each do |m|
class_name = m.sub(/\.rb$/,'').camelize
begin
klass = class_name.split('::').inject(Object){ |klass,part| klass.const_get(part) }
if klass < ActiveRecord::Base && !klass.abstract_class?
puts "Annotating #{class_name}"
self.annotate(klass, header)
else
puts "Skipping #{class_name}"
end
rescue Exception => e
puts "Unable to annotate #{class_name}: #{e.message}"
end
end
end
end